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')
 | 
			
		||||
    elsif log.target_type == 'User' && [:promote, :demote].include?(log.action)
 | 
			
		||||
      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'
 | 
			
		||||
      log.recorded_changes.slice('severity', 'reject_media')
 | 
			
		||||
    elsif log.target_type == 'Status' && log.action == :update
 | 
			
		||||
| 
						 | 
				
			
			@ -84,7 +86,7 @@ module Admin::ActionLogsHelper
 | 
			
		|||
      'positive'
 | 
			
		||||
    when :create
 | 
			
		||||
      opposite_verbs?(log) ? 'negative' : 'positive'
 | 
			
		||||
    when :update, :reset_password, :disable_2fa, :memorialize
 | 
			
		||||
    when :update, :reset_password, :disable_2fa, :memorialize, :change_email
 | 
			
		||||
      'neutral'
 | 
			
		||||
    when :demote, :silence, :disable, :suspend, :remove_avatar, :reopen
 | 
			
		||||
      'negative'
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -124,6 +124,7 @@ class Account < ApplicationRecord
 | 
			
		|||
  scope :matches_domain, ->(value) { where(arel_table[:domain].matches("%#{value}%")) }
 | 
			
		||||
 | 
			
		||||
  delegate :email,
 | 
			
		||||
           :unconfirmed_email,
 | 
			
		||||
           :current_sign_in_ip,
 | 
			
		||||
           :current_sign_in_at,
 | 
			
		||||
           :confirmed?,
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -35,6 +35,11 @@ class Admin::ActionLog < ApplicationRecord
 | 
			
		|||
      self.recorded_changes = target.attributes
 | 
			
		||||
    when :update, :promote, :demote
 | 
			
		||||
      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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -5,6 +5,10 @@ class UserPolicy < ApplicationPolicy
 | 
			
		|||
    staff? && !record.staff?
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def change_email?
 | 
			
		||||
    staff? && !record.staff?
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def disable_2fa?
 | 
			
		||||
    admin? && !record.staff?
 | 
			
		||||
  end
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -36,9 +36,13 @@
 | 
			
		|||
          %th= t('admin.accounts.email')
 | 
			
		||||
          %td
 | 
			
		||||
            = @account.user_email
 | 
			
		||||
 | 
			
		||||
            - if @account.user_confirmed?
 | 
			
		||||
              = 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
 | 
			
		||||
          %th= t('admin.accounts.login_status')
 | 
			
		||||
          %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?
 | 
			
		||||
      avatar: Avatar
 | 
			
		||||
      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
 | 
			
		||||
      confirmed: Confirmed
 | 
			
		||||
      demote: Demote
 | 
			
		||||
| 
						 | 
				
			
			@ -131,6 +138,7 @@ en:
 | 
			
		|||
      statuses: Statuses
 | 
			
		||||
      subscribe: Subscribe
 | 
			
		||||
      title: Accounts
 | 
			
		||||
      unconfirmed_email: Unconfirmed E-mail
 | 
			
		||||
      undo_silenced: Undo silence
 | 
			
		||||
      undo_suspension: Undo suspension
 | 
			
		||||
      unsubscribe: Unsubscribe
 | 
			
		||||
| 
						 | 
				
			
			@ -139,6 +147,7 @@ en:
 | 
			
		|||
    action_logs:
 | 
			
		||||
      actions:
 | 
			
		||||
        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}"
 | 
			
		||||
        create_custom_emoji: "%{name} uploaded new emoji %{target}"
 | 
			
		||||
        create_domain_block: "%{name} blocked domain %{target}"
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -151,6 +151,7 @@ Rails.application.routes.draw do
 | 
			
		|||
        post :memorialize
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      resource :change_email, only: [:show, :update]
 | 
			
		||||
      resource :reset, only: [:create]
 | 
			
		||||
      resource :silence, 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