forked from cybrespace/mastodon
		
	Feature: Allow staff to change user emails (#7074)
* Admin: Show unconfirmed email address on account page * Admin: Allow staff to change user email addresses * ActionLog: On change_email, log current email address and new unconfirmed email address
This commit is contained in:
		
							parent
							
								
									e6e93ecd8a
								
							
						
					
					
						commit
						219a4423d8
					
				
					 10 changed files with 131 additions and 2 deletions
				
			
		
							
								
								
									
										49
									
								
								app/controllers/admin/change_emails_controller.rb
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										49
									
								
								app/controllers/admin/change_emails_controller.rb
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,49 @@
 | 
				
			||||||
 | 
					# frozen_string_literal: true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					module Admin
 | 
				
			||||||
 | 
					  class ChangeEmailsController < BaseController
 | 
				
			||||||
 | 
					    before_action :set_account
 | 
				
			||||||
 | 
					    before_action :require_local_account!
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def show
 | 
				
			||||||
 | 
					      authorize @user, :change_email?
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def update
 | 
				
			||||||
 | 
					      authorize @user, :change_email?
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      new_email = resource_params.fetch(:unconfirmed_email)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      if new_email != @user.email
 | 
				
			||||||
 | 
					        @user.update!(
 | 
				
			||||||
 | 
					          unconfirmed_email: new_email,
 | 
				
			||||||
 | 
					          # Regenerate the confirmation token:
 | 
				
			||||||
 | 
					          confirmation_token: nil
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        log_action :change_email, @user
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        @user.send_confirmation_instructions
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      redirect_to admin_account_path(@account.id), notice: I18n.t('admin.accounts.change_email.changed_msg')
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def set_account
 | 
				
			||||||
 | 
					      @account = Account.find(params[:account_id])
 | 
				
			||||||
 | 
					      @user = @account.user
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def require_local_account!
 | 
				
			||||||
 | 
					      redirect_to admin_account_path(@account.id) unless @account.local? && @account.user.present?
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def resource_params
 | 
				
			||||||
 | 
					      params.require(:user).permit(
 | 
				
			||||||
 | 
					        :unconfirmed_email
 | 
				
			||||||
 | 
					      )
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
| 
						 | 
					@ -45,6 +45,8 @@ module Admin::ActionLogsHelper
 | 
				
			||||||
      log.recorded_changes.slice('domain', 'visible_in_picker')
 | 
					      log.recorded_changes.slice('domain', 'visible_in_picker')
 | 
				
			||||||
    elsif log.target_type == 'User' && [:promote, :demote].include?(log.action)
 | 
					    elsif log.target_type == 'User' && [:promote, :demote].include?(log.action)
 | 
				
			||||||
      log.recorded_changes.slice('moderator', 'admin')
 | 
					      log.recorded_changes.slice('moderator', 'admin')
 | 
				
			||||||
 | 
					    elsif log.target_type == 'User' && [:change_email].include?(log.action)
 | 
				
			||||||
 | 
					      log.recorded_changes.slice('email', 'unconfirmed_email')
 | 
				
			||||||
    elsif log.target_type == 'DomainBlock'
 | 
					    elsif log.target_type == 'DomainBlock'
 | 
				
			||||||
      log.recorded_changes.slice('severity', 'reject_media')
 | 
					      log.recorded_changes.slice('severity', 'reject_media')
 | 
				
			||||||
    elsif log.target_type == 'Status' && log.action == :update
 | 
					    elsif log.target_type == 'Status' && log.action == :update
 | 
				
			||||||
| 
						 | 
					@ -84,7 +86,7 @@ module Admin::ActionLogsHelper
 | 
				
			||||||
      'positive'
 | 
					      'positive'
 | 
				
			||||||
    when :create
 | 
					    when :create
 | 
				
			||||||
      opposite_verbs?(log) ? 'negative' : 'positive'
 | 
					      opposite_verbs?(log) ? 'negative' : 'positive'
 | 
				
			||||||
    when :update, :reset_password, :disable_2fa, :memorialize
 | 
					    when :update, :reset_password, :disable_2fa, :memorialize, :change_email
 | 
				
			||||||
      'neutral'
 | 
					      'neutral'
 | 
				
			||||||
    when :demote, :silence, :disable, :suspend, :remove_avatar, :reopen
 | 
					    when :demote, :silence, :disable, :suspend, :remove_avatar, :reopen
 | 
				
			||||||
      'negative'
 | 
					      'negative'
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -124,6 +124,7 @@ class Account < ApplicationRecord
 | 
				
			||||||
  scope :matches_domain, ->(value) { where(arel_table[:domain].matches("%#{value}%")) }
 | 
					  scope :matches_domain, ->(value) { where(arel_table[:domain].matches("%#{value}%")) }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  delegate :email,
 | 
					  delegate :email,
 | 
				
			||||||
 | 
					           :unconfirmed_email,
 | 
				
			||||||
           :current_sign_in_ip,
 | 
					           :current_sign_in_ip,
 | 
				
			||||||
           :current_sign_in_at,
 | 
					           :current_sign_in_at,
 | 
				
			||||||
           :confirmed?,
 | 
					           :confirmed?,
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -35,6 +35,11 @@ class Admin::ActionLog < ApplicationRecord
 | 
				
			||||||
      self.recorded_changes = target.attributes
 | 
					      self.recorded_changes = target.attributes
 | 
				
			||||||
    when :update, :promote, :demote
 | 
					    when :update, :promote, :demote
 | 
				
			||||||
      self.recorded_changes = target.previous_changes
 | 
					      self.recorded_changes = target.previous_changes
 | 
				
			||||||
 | 
					    when :change_email
 | 
				
			||||||
 | 
					      self.recorded_changes = ActiveSupport::HashWithIndifferentAccess.new(
 | 
				
			||||||
 | 
					        email: [target.email, nil],
 | 
				
			||||||
 | 
					        unconfirmed_email: [nil, target.unconfirmed_email]
 | 
				
			||||||
 | 
					      )
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
end
 | 
					end
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -5,6 +5,10 @@ class UserPolicy < ApplicationPolicy
 | 
				
			||||||
    staff? && !record.staff?
 | 
					    staff? && !record.staff?
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def change_email?
 | 
				
			||||||
 | 
					    staff? && !record.staff?
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def disable_2fa?
 | 
					  def disable_2fa?
 | 
				
			||||||
    admin? && !record.staff?
 | 
					    admin? && !record.staff?
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -36,9 +36,13 @@
 | 
				
			||||||
          %th= t('admin.accounts.email')
 | 
					          %th= t('admin.accounts.email')
 | 
				
			||||||
          %td
 | 
					          %td
 | 
				
			||||||
            = @account.user_email
 | 
					            = @account.user_email
 | 
				
			||||||
 | 
					 | 
				
			||||||
            - if @account.user_confirmed?
 | 
					            - if @account.user_confirmed?
 | 
				
			||||||
              = fa_icon('check')
 | 
					              = fa_icon('check')
 | 
				
			||||||
 | 
					            = table_link_to 'edit', t('admin.accounts.change_email.label'), admin_account_change_email_path(@account.id) if can?(:change_email, @account.user)
 | 
				
			||||||
 | 
					        - if @account.user_unconfirmed_email.present?
 | 
				
			||||||
 | 
					          %th= t('admin.accounts.unconfirmed_email')
 | 
				
			||||||
 | 
					          %td
 | 
				
			||||||
 | 
					            = @account.user_unconfirmed_email
 | 
				
			||||||
        %tr
 | 
					        %tr
 | 
				
			||||||
          %th= t('admin.accounts.login_status')
 | 
					          %th= t('admin.accounts.login_status')
 | 
				
			||||||
          %td
 | 
					          %td
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										7
									
								
								app/views/admin/change_emails/show.html.haml
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								app/views/admin/change_emails/show.html.haml
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,7 @@
 | 
				
			||||||
 | 
					- content_for :page_title do
 | 
				
			||||||
 | 
					  = t('admin.accounts.change_email.title', username: @account.acct)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					= simple_form_for @user, url: admin_account_change_email_path(@account.id) do |f|
 | 
				
			||||||
 | 
					  = f.input :email, wrapper: :with_label, disabled: true, label: t('admin.accounts.change_email.current_email')
 | 
				
			||||||
 | 
					  = f.input :unconfirmed_email, wrapper: :with_label, label: t('admin.accounts.change_email.new_email')
 | 
				
			||||||
 | 
					  = f.button :submit, class: "button", value: t('admin.accounts.change_email.submit')
 | 
				
			||||||
| 
						 | 
					@ -63,6 +63,13 @@ en:
 | 
				
			||||||
      are_you_sure: Are you sure?
 | 
					      are_you_sure: Are you sure?
 | 
				
			||||||
      avatar: Avatar
 | 
					      avatar: Avatar
 | 
				
			||||||
      by_domain: Domain
 | 
					      by_domain: Domain
 | 
				
			||||||
 | 
					      change_email:
 | 
				
			||||||
 | 
					        changed_msg: Account email successfully changed!
 | 
				
			||||||
 | 
					        current_email: Current Email
 | 
				
			||||||
 | 
					        label: Change Email
 | 
				
			||||||
 | 
					        new_email: New Email
 | 
				
			||||||
 | 
					        submit: Change Email
 | 
				
			||||||
 | 
					        title: Change Email for %{username}
 | 
				
			||||||
      confirm: Confirm
 | 
					      confirm: Confirm
 | 
				
			||||||
      confirmed: Confirmed
 | 
					      confirmed: Confirmed
 | 
				
			||||||
      demote: Demote
 | 
					      demote: Demote
 | 
				
			||||||
| 
						 | 
					@ -131,6 +138,7 @@ en:
 | 
				
			||||||
      statuses: Statuses
 | 
					      statuses: Statuses
 | 
				
			||||||
      subscribe: Subscribe
 | 
					      subscribe: Subscribe
 | 
				
			||||||
      title: Accounts
 | 
					      title: Accounts
 | 
				
			||||||
 | 
					      unconfirmed_email: Unconfirmed E-mail
 | 
				
			||||||
      undo_silenced: Undo silence
 | 
					      undo_silenced: Undo silence
 | 
				
			||||||
      undo_suspension: Undo suspension
 | 
					      undo_suspension: Undo suspension
 | 
				
			||||||
      unsubscribe: Unsubscribe
 | 
					      unsubscribe: Unsubscribe
 | 
				
			||||||
| 
						 | 
					@ -139,6 +147,7 @@ en:
 | 
				
			||||||
    action_logs:
 | 
					    action_logs:
 | 
				
			||||||
      actions:
 | 
					      actions:
 | 
				
			||||||
        assigned_to_self_report: "%{name} assigned report %{target} to themselves"
 | 
					        assigned_to_self_report: "%{name} assigned report %{target} to themselves"
 | 
				
			||||||
 | 
					        change_email_user: "%{name} changed the e-mail address of user %{target}"
 | 
				
			||||||
        confirm_user: "%{name} confirmed e-mail address of user %{target}"
 | 
					        confirm_user: "%{name} confirmed e-mail address of user %{target}"
 | 
				
			||||||
        create_custom_emoji: "%{name} uploaded new emoji %{target}"
 | 
					        create_custom_emoji: "%{name} uploaded new emoji %{target}"
 | 
				
			||||||
        create_domain_block: "%{name} blocked domain %{target}"
 | 
					        create_domain_block: "%{name} blocked domain %{target}"
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -151,6 +151,7 @@ Rails.application.routes.draw do
 | 
				
			||||||
        post :memorialize
 | 
					        post :memorialize
 | 
				
			||||||
      end
 | 
					      end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      resource :change_email, only: [:show, :update]
 | 
				
			||||||
      resource :reset, only: [:create]
 | 
					      resource :reset, only: [:create]
 | 
				
			||||||
      resource :silence, only: [:create, :destroy]
 | 
					      resource :silence, only: [:create, :destroy]
 | 
				
			||||||
      resource :suspension, only: [:create, :destroy]
 | 
					      resource :suspension, only: [:create, :destroy]
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										47
									
								
								spec/controllers/admin/change_email_controller_spec.rb
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								spec/controllers/admin/change_email_controller_spec.rb
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,47 @@
 | 
				
			||||||
 | 
					require 'rails_helper'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					RSpec.describe Admin::ChangeEmailsController, type: :controller do
 | 
				
			||||||
 | 
					  render_views
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  let(:admin) { Fabricate(:user, admin: true) }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  before do
 | 
				
			||||||
 | 
					    sign_in admin
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  describe "GET #show" do
 | 
				
			||||||
 | 
					    it "returns http success" do
 | 
				
			||||||
 | 
					      account = Fabricate(:account)
 | 
				
			||||||
 | 
					      user = Fabricate(:user, account: account)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      get :show, params: { account_id: account.id }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      expect(response).to have_http_status(:success)
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  describe "GET #update" do
 | 
				
			||||||
 | 
					    before do
 | 
				
			||||||
 | 
					      allow(UserMailer).to receive(:confirmation_instructions).and_return(double('email', deliver_later: nil))
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    it "returns http success" do
 | 
				
			||||||
 | 
					      account = Fabricate(:account)
 | 
				
			||||||
 | 
					      user = Fabricate(:user, account: account)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      previous_email = user.email
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      post :update, params: { account_id: account.id, user: { unconfirmed_email: 'test@example.com' } }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      user.reload
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      expect(user.email).to eq previous_email
 | 
				
			||||||
 | 
					      expect(user.unconfirmed_email).to eq 'test@example.com'
 | 
				
			||||||
 | 
					      expect(user.confirmation_token).not_to be_nil
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      expect(UserMailer).to have_received(:confirmation_instructions).with(user, user.confirmation_token, { to: 'test@example.com' })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      expect(response).to redirect_to(admin_account_path(account.id))
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
		Loading…
	
	Add table
		
		Reference in a new issue