diff --git a/app/controllers/users/profil_controller.rb b/app/controllers/users/profil_controller.rb
index f55752c11..2c9dd47cc 100644
--- a/app/controllers/users/profil_controller.rb
+++ b/app/controllers/users/profil_controller.rb
@@ -1,6 +1,9 @@
module Users
class ProfilController < UserController
+ before_action :ensure_update_email_is_authorized, only: :update_email
+
def show
+ @waiting_merge_emails = waiting_merge_emails
@waiting_transfers = current_user.dossiers.joins(:transfer).group('dossier_transfers.email').count.to_a
end
@@ -11,13 +14,16 @@ module Users
end
def update_email
- if current_user.instructeur? && !target_email_allowed?
- flash.alert = t('.email_not_allowed', contact_email: CONTACT_EMAIL, requested_email: requested_email)
- elsif current_user.update(update_email_params)
+ requested_user = User.find_by(email: requested_email)
+
+ if requested_user.present?
+ current_user.ask_for_merge(requested_user)
+ current_user.update(unconfirmed_email: nil)
+
flash.notice = t('devise.registrations.update_needs_confirmation')
- elsif current_user.errors&.details&.dig(:email)&.any? { |e| e[:error] == :taken }
- UserMailer.account_already_taken(current_user, requested_email).deliver_later
- # avoid leaking information about whether an account with this email exists or not
+ elsif current_user.update(update_email_params)
+ current_user.update(requested_merge_into: nil)
+
flash.notice = t('devise.registrations.update_needs_confirmation')
else
flash.alert = current_user.errors.full_messages
@@ -32,8 +38,39 @@ module Users
redirect_to profil_path
end
+ def accept_merge
+ users_requesting_merge.each { |user| current_user.merge(user) }
+ users_requesting_merge.update_all(requested_merge_into_id: nil)
+
+ flash.notice = "Vous avez absorbé le compte #{waiting_merge_emails.join(', ')}"
+ redirect_to profil_path
+ end
+
+ def refuse_merge
+ users = users_requesting_merge
+ users.update_all(requested_merge_into_id: nil)
+
+ flash.notice = 'La fusion a été refusé'
+ redirect_to profil_path
+ end
+
private
+ def waiting_merge_emails
+ users_requesting_merge.pluck(:email)
+ end
+
+ def users_requesting_merge
+ @requesting_merge ||= current_user.requested_merge_from
+ end
+
+ def ensure_update_email_is_authorized
+ if current_user.instructeur? && !target_email_allowed?
+ flash.alert = t('users.profil.ensure_update_email_is_authorized.email_not_allowed', contact_email: CONTACT_EMAIL, requested_email: requested_email)
+ redirect_to profil_path
+ end
+ end
+
def update_email_params
params.require(:user).permit(:email)
end
diff --git a/app/mailers/user_mailer.rb b/app/mailers/user_mailer.rb
index 6fe55a4a0..e5b143566 100644
--- a/app/mailers/user_mailer.rb
+++ b/app/mailers/user_mailer.rb
@@ -12,10 +12,10 @@ class UserMailer < ApplicationMailer
mail(to: user.email, subject: @subject, procedure: @procedure)
end
- def account_already_taken(user, requested_email)
+ def ask_for_merge(user, requested_email)
@user = user
@requested_email = requested_email
- @subject = "Changement d’adresse email"
+ @subject = "Fusion de compte"
mail(to: requested_email, subject: @subject)
end
diff --git a/app/models/user.rb b/app/models/user.rb
index 24656f846..fba5d1190 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -28,6 +28,7 @@
# administrateur_id :bigint
# expert_id :bigint
# instructeur_id :bigint
+# requested_merge_into_id :bigint
#
class User < ApplicationRecord
include EmailSanitizableConcern
@@ -48,10 +49,13 @@ class User < ApplicationRecord
has_many :dossiers_invites, through: :invites, source: :dossier
has_many :deleted_dossiers
has_many :merge_logs, dependent: :destroy
+ has_many :requested_merge_from, class_name: 'User', dependent: :nullify, inverse_of: :requested_merge_into, foreign_key: :requested_merge_into_id
+
has_one :france_connect_information, dependent: :destroy
belongs_to :instructeur, optional: true, dependent: :destroy
belongs_to :administrateur, optional: true, dependent: :destroy
belongs_to :expert, optional: true, dependent: :destroy
+ belongs_to :requested_merge_into, class_name: 'User', optional: true
accepts_nested_attributes_for :france_connect_information
@@ -218,6 +222,11 @@ class User < ApplicationRecord
end
end
+ def ask_for_merge(requested_user)
+ update(requested_merge_into: requested_user)
+ UserMailer.ask_for_merge(self, requested_user.email).deliver_later
+ end
+
private
def link_invites!
diff --git a/app/views/user_mailer/account_already_taken.haml b/app/views/user_mailer/account_already_taken.haml
deleted file mode 100644
index 826cdbb37..000000000
--- a/app/views/user_mailer/account_already_taken.haml
+++ /dev/null
@@ -1,20 +0,0 @@
-- content_for(:title, @subject)
-
-%p
- Bonjour,
-
-%p
- L’utilisateur « #{@user.email} » a demandé le changement de son adresse vers « #{@requested_email} ».
-
-%p
- Malheureusement, votre compte « #{@requested_email} » existe déjà. Nous ne pouvons pas fusionner automatiquement vos comptes.
-
-%p
- %strong Nous ne pouvons donc pas effectuer le changement d’adresse email.
-
-%p
- Si vous n’êtes pas à l’origine de cette demande, vous pouvez ignorer ce message. Et si vous avez besoin d’assistance, n’hésitez pas à nous contacter à
- = succeed '.' do
- = mail_to CONTACT_EMAIL
-
-= render partial: "layouts/mailers/signature"
diff --git a/app/views/user_mailer/ask_for_merge.haml b/app/views/user_mailer/ask_for_merge.haml
new file mode 100644
index 000000000..d61072a89
--- /dev/null
+++ b/app/views/user_mailer/ask_for_merge.haml
@@ -0,0 +1,22 @@
+- content_for(:title, @subject)
+
+%p
+ Bonjour,
+
+%p
+ L’utilisateur « #{@user.email} » a demandé la fusion de son compte avec le votre « #{@requested_email} ».
+
+%p
+ Si vous désirez confirmer la fusion de ces comptes :
+
+%ol
+ %li connectez-vous avec le compte #{@requested_email}
+ %li allez sur votre page profil
+ %li acceptez la fusion
+
+%p
+ Si vous n’êtes pas à l’origine de cette demande, vous pouvez ignorer ce message. Et si vous avez besoin d’assistance, n’hésitez pas à nous contacter à
+ = succeed '.' do
+ = mail_to CONTACT_EMAIL
+
+= render partial: "layouts/mailers/signature"
diff --git a/app/views/users/profil/show.html.haml b/app/views/users/profil/show.html.haml
index 46f60a875..d7946c721 100644
--- a/app/views/users/profil/show.html.haml
+++ b/app/views/users/profil/show.html.haml
@@ -5,18 +5,30 @@
#profil-page.container
%h1 Profil
+ - if @waiting_merge_emails.any?
+ .card
+ .card-title Demande de fusion de comptes
+ %p
+ Acceptez-vous d’absorber le compte de
+ %span.email-address= @waiting_merge_emails.join(', ')
+
+ = link_to 'Refuser la fusion', refuse_merge_path, method: :post, class: 'button', data: { confirm: "Confirmez-vous le refus ?" }
+ = link_to 'Accepter la fusion', accept_merge_path, method: :post, class: 'button', data: { confirm: "Confirmez-vous la fusion des comptes ?" }
+
.card
.card-title Coordonnées
%p
Votre email est actuellement
%span.email-address= current_user.email
- - if current_user.unconfirmed_email.present?
- .card.warning
- .card-title
- Changement en attente :
- %span.email-address= current_user.unconfirmed_email
- %p
- Pour finaliser votre changement d’adresse, vérifiez vos emails et cliquez sur le lien de confirmation.
+
+ - waiting_email = current_user.unconfirmed_email || current_user.requested_merge_into&.email
+
+ - if waiting_email.present?
+ %p.mb-4
+ Changement en attente :
+ %span.email-address= waiting_email
+ %br
+ Pour finaliser votre changement d’adresse, vérifiez vos emails et cliquez sur le lien de confirmation.
- if current_user.instructeur?
%p.mb-4
diff --git a/config/locales/views/users/profil/fr.yml b/config/locales/views/users/profil/fr.yml
index 8062880ff..4c18b6d04 100644
--- a/config/locales/views/users/profil/fr.yml
+++ b/config/locales/views/users/profil/fr.yml
@@ -21,7 +21,7 @@ fr:
Si ce n'est pas votre cas, contactez le support :
%{contact_email}
- update_email:
+ ensure_update_email_is_authorized:
email_not_allowed: "L’email %{requested_email} ne peut être utilisé, contactez le support : %{contact_email}"
transfer_all_dossiers:
new_transfer:
diff --git a/config/routes.rb b/config/routes.rb
index 249faa38c..45cd42836 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -280,6 +280,8 @@ Rails.application.routes.draw do
get 'renew-api-token' => redirect('/profil')
patch 'update_email' => 'profil#update_email'
post 'transfer_all_dossiers' => 'profil#transfer_all_dossiers'
+ post 'accept_merge' => 'profil#accept_merge'
+ post 'refuse_merge' => 'profil#refuse_merge'
end
#
diff --git a/db/migrate/20211026082232_add_requested_merge_into_column_to_users.rb b/db/migrate/20211026082232_add_requested_merge_into_column_to_users.rb
new file mode 100644
index 000000000..aec5a9326
--- /dev/null
+++ b/db/migrate/20211026082232_add_requested_merge_into_column_to_users.rb
@@ -0,0 +1,5 @@
+class AddRequestedMergeIntoColumnToUsers < ActiveRecord::Migration[6.1]
+ def change
+ add_reference :users, :requested_merge_into, foreign_key: { to_table: :users }, null: true, index: true
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index ede968b34..c992f0ae8 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -794,11 +794,13 @@ ActiveRecord::Schema.define(version: 2021_10_26_131800) do
t.bigint "administrateur_id"
t.bigint "expert_id"
t.string "locale"
+ t.bigint "requested_merge_into_id"
t.index ["administrateur_id"], name: "index_users_on_administrateur_id"
t.index ["confirmation_token"], name: "index_users_on_confirmation_token", unique: true
t.index ["email"], name: "index_users_on_email", unique: true
t.index ["expert_id"], name: "index_users_on_expert_id"
t.index ["instructeur_id"], name: "index_users_on_instructeur_id"
+ t.index ["requested_merge_into_id"], name: "index_users_on_requested_merge_into_id"
t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true
t.index ["unlock_token"], name: "index_users_on_unlock_token", unique: true
end
@@ -868,5 +870,6 @@ ActiveRecord::Schema.define(version: 2021_10_26_131800) do
add_foreign_key "users", "administrateurs"
add_foreign_key "users", "experts"
add_foreign_key "users", "instructeurs"
+ add_foreign_key "users", "users", column: "requested_merge_into_id"
add_foreign_key "without_continuation_mails", "procedures"
end
diff --git a/spec/controllers/users/profil_controller_spec.rb b/spec/controllers/users/profil_controller_spec.rb
index bb8cf8fcc..6e3bd3779 100644
--- a/spec/controllers/users/profil_controller_spec.rb
+++ b/spec/controllers/users/profil_controller_spec.rb
@@ -49,12 +49,16 @@ describe Users::ProfilController, type: :controller do
describe 'PATCH #update_email' do
context 'when everything is fine' do
+ let(:previous_request) { create(:user) }
+
before do
+ user.update(requested_merge_into: previous_request)
patch :update_email, params: { user: { email: 'loulou@lou.com' } }
user.reload
end
it { expect(user.unconfirmed_email).to eq('loulou@lou.com') }
+ it { expect(user.requested_merge_into).to be_nil }
it { expect(response).to redirect_to(profil_path) }
it { expect(flash.notice).to eq(I18n.t('devise.registrations.update_needs_confirmation')) }
end
@@ -63,16 +67,21 @@ describe Users::ProfilController, type: :controller do
let(:existing_user) { create(:user) }
before do
+ user.update(unconfirmed_email: 'unconfirmed@mail.com')
+
+ expect_any_instance_of(User).to receive(:ask_for_merge).with(existing_user)
+
perform_enqueued_jobs do
patch :update_email, params: { user: { email: existing_user.email } }
end
user.reload
end
- it { expect(user.unconfirmed_email).to be_nil }
- it { expect(ActionMailer::Base.deliveries.last.to).to eq([existing_user.email]) }
- it { expect(response).to redirect_to(profil_path) }
- it { expect(flash.notice).to eq(I18n.t('devise.registrations.update_needs_confirmation')) }
+ it 'launches the merge process' do
+ expect(user.unconfirmed_email).to be_nil
+ expect(response).to redirect_to(profil_path)
+ expect(flash.notice).to eq(I18n.t('devise.registrations.update_needs_confirmation'))
+ end
end
context 'when the mail is incorrect' do
@@ -126,4 +135,38 @@ describe Users::ProfilController, type: :controller do
expect(flash.notice).to eq("Le transfert de 3 dossiers à #{next_owner} est en cours")
end
end
+
+ context 'POST #accept_merge' do
+ let!(:requesting_user) { create(:user, requested_merge_into: user) }
+
+ subject { post :accept_merge }
+
+ it 'merges the account' do
+ expect_any_instance_of(User).to receive(:merge)
+
+ subject
+ requesting_user.reload
+
+ expect(requesting_user.requested_merge_into).to be_nil
+ expect(flash.notice).to include('Vous avez absorbé')
+ expect(response).to redirect_to(profil_path)
+ end
+ end
+
+ context 'POST #refuse_merge' do
+ let!(:requesting_user) { create(:user, requested_merge_into: user) }
+
+ subject { post :refuse_merge }
+
+ it 'merges the account' do
+ expect_any_instance_of(User).not_to receive(:merge)
+
+ subject
+ requesting_user.reload
+
+ expect(requesting_user.requested_merge_into).to be_nil
+ expect(flash.notice).to include('La fusion a été refusé')
+ expect(response).to redirect_to(profil_path)
+ end
+ end
end
diff --git a/spec/mailers/previews/user_mailer_preview.rb b/spec/mailers/previews/user_mailer_preview.rb
index bf20eec17..436ac435f 100644
--- a/spec/mailers/previews/user_mailer_preview.rb
+++ b/spec/mailers/previews/user_mailer_preview.rb
@@ -8,8 +8,8 @@ class UserMailerPreview < ActionMailer::Preview
UserMailer.new_account_warning(user, procedure)
end
- def account_already_taken
- UserMailer.account_already_taken(user, 'dircab@territoires.gouv.fr')
+ def ask_for_merge
+ UserMailer.ask_for_merge(user, 'dircab@territoires.gouv.fr')
end
private
diff --git a/spec/mailers/user_mailer_spec.rb b/spec/mailers/user_mailer_spec.rb
index 651dcc4e8..88f4fa2dc 100644
--- a/spec/mailers/user_mailer_spec.rb
+++ b/spec/mailers/user_mailer_spec.rb
@@ -17,10 +17,10 @@ RSpec.describe UserMailer, type: :mailer do
end
end
- describe '.account_already_taken' do
+ describe '.ask_for_merge' do
let(:requested_email) { 'new@exemple.fr' }
- subject { described_class.account_already_taken(user, requested_email) }
+ subject { described_class.ask_for_merge(user, requested_email) }
it { expect(subject.to).to eq([requested_email]) }
it { expect(subject.body).to include(requested_email) }
diff --git a/spec/system/users/change_email_spec.rb b/spec/system/users/change_email_spec.rb
index c2a8bfcf0..76ad23469 100644
--- a/spec/system/users/change_email_spec.rb
+++ b/spec/system/users/change_email_spec.rb
@@ -29,3 +29,36 @@ describe 'Changing an email' do
expect(user.reload.email).to eq(new_email)
end
end
+
+describe 'Merging account' do
+ let(:old_user) { create(:user) }
+ let(:new_user) { create(:user) }
+
+ before do
+ login_as old_user, scope: :user
+ end
+
+ scenario 'is easy' do
+ visit '/profil'
+
+ fill_in :user_email, with: new_user.email
+
+ perform_enqueued_jobs do
+ click_button 'Changer mon adresse'
+ end
+
+ expect(page).to have_content(I18n.t('devise.registrations.update_needs_confirmation'))
+ expect(page).to have_content(old_user.email)
+ expect(page).to have_content(new_user.email)
+
+ login_as new_user, scope: :user
+ visit '/profil'
+
+ expect(page).to have_content("Acceptez-vous d’absorber le compte de #{old_user.email}")
+ click_on 'Accepter la fusion'
+
+ expect(page).not_to have_content(old_user.email)
+ expect(page).to have_content(new_user.email)
+ expect { old_user.reload }.to raise_error(ActiveRecord::RecordNotFound)
+ end
+end