2018-03-06 13:44:29 +01:00
|
|
|
class User < ApplicationRecord
|
2024-03-18 19:31:57 +01:00
|
|
|
include DomainMigratableConcern
|
2018-02-28 14:31:03 +01:00
|
|
|
include EmailSanitizableConcern
|
2021-08-26 19:23:01 +02:00
|
|
|
include PasswordComplexityConcern
|
2018-02-28 14:31:03 +01:00
|
|
|
|
2017-05-26 21:31:51 +02:00
|
|
|
enum loged_in_with_france_connect: {
|
|
|
|
particulier: 'particulier',
|
|
|
|
entreprise: 'entreprise'
|
|
|
|
}
|
2015-12-24 10:12:23 +01:00
|
|
|
|
2015-09-23 10:02:01 +02:00
|
|
|
# Include default devise modules. Others available are:
|
|
|
|
# :confirmable, :lockable, :timeoutable and :omniauthable
|
2022-04-19 18:17:49 +02:00
|
|
|
devise :database_authenticatable, :registerable,
|
2019-05-22 18:33:00 +02:00
|
|
|
:recoverable, :rememberable, :trackable, :validatable, :confirmable, :lockable
|
2015-09-23 12:16:21 +02:00
|
|
|
|
2022-10-13 17:45:33 +02:00
|
|
|
# We should never cascade delete dossiers. In normal case we call delete_and_keep_track_dossiers
|
|
|
|
# before deleting a user (which dissociate dossiers from the user).
|
|
|
|
# Destroying a user with dossier is always a mistake.
|
|
|
|
has_many :dossiers, dependent: :restrict_with_exception
|
2022-05-23 15:09:22 +02:00
|
|
|
has_many :targeted_user_links, dependent: :destroy
|
2016-02-08 18:16:18 +01:00
|
|
|
has_many :invites, dependent: :destroy
|
2018-04-03 10:48:46 +02:00
|
|
|
has_many :dossiers_invites, through: :invites, source: :dossier
|
2020-12-07 15:10:26 +01:00
|
|
|
has_many :deleted_dossiers
|
2021-10-26 14:41:30 +02:00
|
|
|
has_many :merge_logs, dependent: :destroy
|
2021-11-04 15:51:54 +01:00
|
|
|
has_many :requested_merge_from, class_name: 'User', dependent: :nullify, inverse_of: :requested_merge_into, foreign_key: :requested_merge_into_id
|
2024-03-05 14:00:54 +01:00
|
|
|
has_many :france_connect_informations, dependent: :destroy
|
2021-11-04 15:51:54 +01:00
|
|
|
|
2022-03-15 17:28:22 +01:00
|
|
|
has_one :instructeur, dependent: :destroy
|
|
|
|
has_one :administrateur, dependent: :destroy
|
2023-09-04 12:07:04 +02:00
|
|
|
has_one :gestionnaire, dependent: :destroy
|
2022-03-15 17:28:22 +01:00
|
|
|
has_one :expert, dependent: :destroy
|
2021-10-26 12:54:50 +02:00
|
|
|
belongs_to :requested_merge_into, class_name: 'User', optional: true
|
2015-10-06 11:21:20 +02:00
|
|
|
|
2024-03-05 14:00:54 +01:00
|
|
|
accepts_nested_attributes_for :france_connect_informations
|
2017-02-07 16:56:21 +01:00
|
|
|
|
2021-02-28 22:19:22 +01:00
|
|
|
default_scope { eager_load(:instructeur, :administrateur, :expert) }
|
2016-10-11 10:31:32 +02:00
|
|
|
|
2024-02-12 12:39:43 +01:00
|
|
|
before_validation -> { sanitize_email(:email) }
|
2021-12-23 14:54:37 +01:00
|
|
|
validate :does_not_merge_on_self, if: :requested_merge_into_id_changed?
|
|
|
|
|
2024-02-15 09:45:20 +01:00
|
|
|
before_validation :remove_devise_email_format_validator
|
|
|
|
# plug our custom validation a la devise (same options) https://github.com/heartcombo/devise/blob/main/lib/devise/models/validatable.rb#L30
|
|
|
|
validates :email, strict_email: true, allow_blank: true, if: :devise_will_save_change_to_email?
|
2024-02-12 12:39:43 +01:00
|
|
|
|
2021-08-26 19:23:01 +02:00
|
|
|
def validate_password_complexity?
|
|
|
|
administrateur?
|
|
|
|
end
|
2020-09-17 15:53:03 +02:00
|
|
|
|
2020-02-25 15:12:09 +01:00
|
|
|
# Override of Devise::Models::Confirmable#send_confirmation_instructions
|
|
|
|
def send_confirmation_instructions
|
|
|
|
unless @raw_confirmation_token
|
|
|
|
generate_confirmation_token!
|
|
|
|
end
|
|
|
|
|
|
|
|
opts = pending_reconfirmation? ? { to: unconfirmed_email } : {}
|
|
|
|
|
|
|
|
# Make our procedure_after_confirmation available to the Mailer
|
|
|
|
opts[:procedure_after_confirmation] = CurrentConfirmation.procedure_after_confirmation
|
2023-01-03 14:46:10 +01:00
|
|
|
opts[:prefill_token] = CurrentConfirmation.prefill_token
|
2020-02-25 15:12:09 +01:00
|
|
|
|
|
|
|
send_devise_notification(:confirmation_instructions, @raw_confirmation_token, opts)
|
|
|
|
end
|
|
|
|
|
2018-09-19 11:56:05 +02:00
|
|
|
# Callback provided by Devise
|
|
|
|
def after_confirmation
|
|
|
|
link_invites!
|
|
|
|
end
|
|
|
|
|
2018-05-30 18:26:23 +02:00
|
|
|
def owns?(dossier)
|
|
|
|
dossier.user_id == id
|
|
|
|
end
|
|
|
|
|
2023-04-03 20:00:52 +02:00
|
|
|
def invite?(dossier)
|
2023-04-04 15:02:20 +02:00
|
|
|
invites.exists?(dossier:)
|
2016-03-22 17:36:36 +01:00
|
|
|
end
|
2018-05-30 18:31:02 +02:00
|
|
|
|
|
|
|
def owns_or_invite?(dossier)
|
2023-04-03 20:00:52 +02:00
|
|
|
owns?(dossier) || invite?(dossier)
|
2018-05-30 18:31:02 +02:00
|
|
|
end
|
2018-09-19 11:56:05 +02:00
|
|
|
|
2019-08-07 15:52:38 +02:00
|
|
|
def invite!
|
|
|
|
UserMailer.invite_instructeur(self, set_reset_password_token).deliver_later
|
|
|
|
end
|
|
|
|
|
2023-09-04 12:07:04 +02:00
|
|
|
def invite_gestionnaire!(groupe_gestionnaire)
|
|
|
|
UserMailer.invite_gestionnaire(self, set_reset_password_token, groupe_gestionnaire).deliver_later
|
2023-08-25 22:53:30 +02:00
|
|
|
end
|
|
|
|
|
2024-03-19 17:17:44 +01:00
|
|
|
def invite_administrateur!
|
|
|
|
AdministrationMailer.invite_admin(self, set_reset_password_token).deliver_later
|
2019-08-09 11:41:36 +02:00
|
|
|
end
|
|
|
|
|
2019-08-09 16:04:28 +02:00
|
|
|
def remind_invitation!
|
|
|
|
reset_password_token = set_reset_password_token
|
|
|
|
|
|
|
|
AdministrateurMailer.activate_before_expiration(self, reset_password_token).deliver_later
|
|
|
|
end
|
2019-08-09 11:41:36 +02:00
|
|
|
|
2019-08-16 18:15:05 +02:00
|
|
|
def self.create_or_promote_to_instructeur(email, password, administrateurs: [])
|
|
|
|
user = User
|
|
|
|
.create_with(password: password, confirmed_at: Time.zone.now)
|
|
|
|
.find_or_create_by(email: email)
|
|
|
|
|
|
|
|
if user.valid?
|
2022-03-15 17:28:22 +01:00
|
|
|
if user.instructeur.nil?
|
2019-10-16 14:15:47 +02:00
|
|
|
user.create_instructeur!
|
2024-03-05 14:00:54 +01:00
|
|
|
user.france_connect_informations.delete_all
|
2019-08-16 18:15:05 +02:00
|
|
|
end
|
|
|
|
|
|
|
|
user.instructeur.administrateurs << administrateurs
|
|
|
|
end
|
|
|
|
|
|
|
|
user
|
|
|
|
end
|
|
|
|
|
2023-09-04 12:07:04 +02:00
|
|
|
def self.create_or_promote_to_gestionnaire(email, password)
|
2023-08-25 22:53:30 +02:00
|
|
|
user = User.create_or_promote_to_administrateur(email, password)
|
|
|
|
|
2023-09-04 12:07:04 +02:00
|
|
|
if user.valid? && user.gestionnaire.nil?
|
|
|
|
user.create_gestionnaire!
|
2023-08-25 22:53:30 +02:00
|
|
|
end
|
|
|
|
|
|
|
|
user
|
|
|
|
end
|
|
|
|
|
2019-08-20 12:07:07 +02:00
|
|
|
def self.create_or_promote_to_administrateur(email, password)
|
|
|
|
user = User.create_or_promote_to_instructeur(email, password)
|
|
|
|
|
2022-03-15 17:28:22 +01:00
|
|
|
if user.valid? && user.administrateur.nil?
|
2020-02-03 11:09:54 +01:00
|
|
|
user.create_administrateur!
|
2024-03-05 14:00:54 +01:00
|
|
|
user.france_connect_informations.delete_all
|
2023-04-05 12:36:11 +02:00
|
|
|
AdminUpdateDefaultZonesJob.perform_later(user.administrateur)
|
2019-08-20 12:07:07 +02:00
|
|
|
end
|
|
|
|
|
|
|
|
user
|
|
|
|
end
|
|
|
|
|
2021-01-15 16:33:36 +01:00
|
|
|
def self.create_or_promote_to_expert(email, password)
|
|
|
|
user = User
|
|
|
|
.create_with(password: password, confirmed_at: Time.zone.now)
|
|
|
|
.find_or_create_by(email: email)
|
|
|
|
|
|
|
|
if user.valid?
|
2022-03-15 17:28:22 +01:00
|
|
|
if user.expert.nil?
|
2021-01-15 16:33:36 +01:00
|
|
|
user.create_expert!
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
user
|
|
|
|
end
|
|
|
|
|
2019-07-04 12:36:17 +02:00
|
|
|
def flipper_id
|
|
|
|
"User:#{id}"
|
|
|
|
end
|
|
|
|
|
2019-11-05 10:01:07 +01:00
|
|
|
def active?
|
|
|
|
last_sign_in_at.present?
|
|
|
|
end
|
|
|
|
|
2021-02-01 14:28:04 +01:00
|
|
|
def administrateur?
|
2022-03-15 17:28:22 +01:00
|
|
|
administrateur.present?
|
2021-02-01 14:28:04 +01:00
|
|
|
end
|
|
|
|
|
|
|
|
def instructeur?
|
2022-03-15 17:28:22 +01:00
|
|
|
instructeur.present?
|
2021-02-01 14:28:04 +01:00
|
|
|
end
|
|
|
|
|
2023-09-04 12:07:04 +02:00
|
|
|
def gestionnaire?
|
|
|
|
gestionnaire.present?
|
2023-08-25 22:53:30 +02:00
|
|
|
end
|
|
|
|
|
2021-04-14 15:54:30 +02:00
|
|
|
def expert?
|
2022-03-15 17:28:22 +01:00
|
|
|
expert.present?
|
2021-04-14 15:54:30 +02:00
|
|
|
end
|
|
|
|
|
2021-02-01 14:28:04 +01:00
|
|
|
def can_france_connect?
|
|
|
|
!administrateur? && !instructeur?
|
|
|
|
end
|
|
|
|
|
2024-03-05 14:00:54 +01:00
|
|
|
def france_connected_with_one_identity?
|
|
|
|
france_connect_informations.size == 1
|
|
|
|
end
|
|
|
|
|
2020-01-06 17:33:09 +01:00
|
|
|
def can_be_deleted?
|
2021-05-01 12:20:24 +02:00
|
|
|
!administrateur? && !instructeur? && !expert?
|
2020-01-06 17:33:09 +01:00
|
|
|
end
|
|
|
|
|
2023-11-17 10:22:34 +01:00
|
|
|
def delete_and_keep_track_dossiers_also_delete_user(super_admin, reason:)
|
2020-01-08 17:00:00 +01:00
|
|
|
if !can_be_deleted?
|
2021-05-01 12:20:24 +02:00
|
|
|
raise "Cannot delete this user because they are also instructeur, expert or administrateur"
|
2020-01-08 17:00:00 +01:00
|
|
|
end
|
|
|
|
|
2021-10-26 11:13:42 +02:00
|
|
|
transaction do
|
2022-03-09 10:27:43 +01:00
|
|
|
# delete invites
|
|
|
|
Invite.where(dossier: dossiers).destroy_all
|
|
|
|
|
2023-11-17 10:22:34 +01:00
|
|
|
delete_and_keep_track_dossiers(super_admin, reason: :user_removed)
|
2022-11-14 16:47:53 +01:00
|
|
|
destroy!
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2023-11-17 10:22:34 +01:00
|
|
|
def delete_and_keep_track_dossiers(super_admin, reason:)
|
2022-11-14 16:47:53 +01:00
|
|
|
transaction do
|
2022-03-09 10:27:43 +01:00
|
|
|
# delete dossiers brouillon
|
|
|
|
dossiers.state_brouillon.each do |dossier|
|
2023-11-17 10:22:34 +01:00
|
|
|
dossier.hide_and_keep_track!(dossier.user, reason)
|
2022-03-09 10:27:43 +01:00
|
|
|
end
|
|
|
|
dossiers.state_brouillon.find_each(&:purge_discarded)
|
|
|
|
|
|
|
|
# delete dossiers en_construction
|
2021-10-26 11:13:42 +02:00
|
|
|
dossiers.state_en_construction.each do |dossier|
|
2023-11-17 10:22:34 +01:00
|
|
|
dossier.hide_and_keep_track!(dossier.user, reason)
|
2022-03-09 10:27:43 +01:00
|
|
|
end
|
|
|
|
dossiers.state_en_construction.find_each(&:purge_discarded)
|
|
|
|
|
|
|
|
# delete dossiers terminé
|
|
|
|
dossiers.state_termine.each do |dossier|
|
2023-11-17 10:22:34 +01:00
|
|
|
dossier.hide_and_keep_track!(dossier.user, reason)
|
2021-10-26 11:13:42 +02:00
|
|
|
end
|
|
|
|
dossiers.update_all(deleted_user_email_never_send: email, user_id: nil, dossier_transfer_id: nil)
|
2020-01-08 10:50:16 +01:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2021-10-22 15:17:25 +02:00
|
|
|
def merge(old_user)
|
2022-10-13 16:44:20 +02:00
|
|
|
raise "Merging same user, no way" if old_user.id == self.id
|
2021-10-26 15:50:38 +02:00
|
|
|
transaction do
|
2022-03-09 10:27:43 +01:00
|
|
|
old_user.dossiers.update_all(user_id: id)
|
2021-10-26 15:50:38 +02:00
|
|
|
old_user.invites.update_all(user_id: id)
|
|
|
|
old_user.merge_logs.update_all(user_id: id)
|
2022-05-23 15:09:22 +02:00
|
|
|
old_user.targeted_user_links.update_all(user_id: id)
|
2021-10-26 15:50:38 +02:00
|
|
|
|
2022-03-15 13:51:01 +01:00
|
|
|
# Move or merge old user's roles to the user
|
2021-10-26 15:50:38 +02:00
|
|
|
[
|
|
|
|
[old_user.instructeur, instructeur],
|
|
|
|
[old_user.expert, expert],
|
|
|
|
[old_user.administrateur, administrateur]
|
|
|
|
].each do |old_role, targeted_role|
|
|
|
|
if targeted_role.nil?
|
|
|
|
old_role&.update(user: self)
|
|
|
|
else
|
|
|
|
targeted_role.merge(old_role)
|
|
|
|
end
|
2021-10-22 15:17:25 +02:00
|
|
|
end
|
2022-03-15 13:51:01 +01:00
|
|
|
# (Ensure the old user doesn't reference its former roles anymore)
|
|
|
|
old_user.reload
|
2021-10-28 12:09:30 +02:00
|
|
|
|
2021-10-26 15:50:38 +02:00
|
|
|
merge_logs.create(from_user_id: old_user.id, from_user_email: old_user.email)
|
|
|
|
old_user.destroy
|
|
|
|
end
|
2021-10-22 15:17:25 +02:00
|
|
|
end
|
|
|
|
|
2021-10-26 13:36:14 +02:00
|
|
|
def ask_for_merge(requested_user)
|
2021-12-23 14:54:37 +01:00
|
|
|
if update(requested_merge_into: requested_user)
|
|
|
|
UserMailer.ask_for_merge(self, requested_user.email).deliver_later
|
|
|
|
return true
|
|
|
|
else
|
|
|
|
return false
|
|
|
|
end
|
2021-10-26 13:36:14 +02:00
|
|
|
end
|
|
|
|
|
2022-04-19 18:17:49 +02:00
|
|
|
def send_devise_notification(notification, *args)
|
|
|
|
devise_mailer.send(notification, self, *args).deliver_later
|
|
|
|
end
|
|
|
|
|
2023-07-18 13:53:06 +02:00
|
|
|
def active_for_authentication?
|
|
|
|
super && blocked_at.nil?
|
|
|
|
end
|
|
|
|
|
2018-09-19 11:56:05 +02:00
|
|
|
private
|
|
|
|
|
2021-12-23 14:54:37 +01:00
|
|
|
def does_not_merge_on_self
|
|
|
|
return if requested_merge_into_id != self.id
|
|
|
|
errors.add(:requested_merge_into, :same)
|
|
|
|
end
|
|
|
|
|
2018-09-19 11:56:05 +02:00
|
|
|
def link_invites!
|
|
|
|
Invite.where(email: email).update_all(user_id: id)
|
|
|
|
end
|
2024-02-12 12:39:43 +01:00
|
|
|
|
2024-02-15 09:45:20 +01:00
|
|
|
# we just want to remove the devise format validator
|
|
|
|
# https://github.com/heartcombo/devise/blob/main/lib/devise/models/validatable.rb#L30
|
|
|
|
def remove_devise_email_format_validator
|
2024-02-12 12:39:43 +01:00
|
|
|
_validators[:email]&.reject! { _1.is_a?(ActiveModel::Validations::FormatValidator) }
|
|
|
|
_validate_callbacks.each do |callback|
|
|
|
|
next if !callback.filter.is_a?(ActiveModel::Validations::FormatValidator)
|
|
|
|
next if !callback.filter.attributes.include? :email
|
|
|
|
|
|
|
|
callback.filter.attributes.delete(:email)
|
|
|
|
end
|
|
|
|
end
|
2015-09-23 10:02:01 +02:00
|
|
|
end
|