app/controllers/devise/masquerades_controller.rb
require 'securerandom'
class Devise::MasqueradesController < DeviseController
Devise.mappings.each do |name, _|
class_eval <<-METHODS, __FILE__, __LINE__ + 1
skip_before_action :masquerade_#{name}!, raise: false
METHODS
end
skip_before_action :masquerade!, raise: false
prepend_before_action :authenticate_scope!, only: :show
prepend_before_action :masquerade_authorize!
def show
if send("#{masqueraded_resource_name}_masquerade?")
resource = masquerading_current_user
go_back(resource, path: after_masquerade_full_path_for(resource))
else
masqueradable_resource = find_masqueradable_resource
save_masquerade_owner_session(masqueradable_resource)
resource = masqueradable_resource
sign_out(masquerading_current_user)
unless resource
flash[:error] = "#{masqueraded_resource_class} not found."
redirect_to(send("new_#{masqueraded_resource_name}_session_path")) and return
end
request.env['devise.skip_trackable'] = '1'
masquerade_sign_in(resource)
go_back(resource, path: after_masquerade_full_path_for(resource))
end
end
def back
unless send("#{masqueraded_resource_name}_masquerade?")
resource = send("current_#{masqueraded_resource_name}")
go_back(resource, path: after_back_masquerade_path_for(resource))
else
masqueradable_resource = send("current_#{masqueraded_resource_name}")
unless send("#{masqueraded_resource_name}_signed_in?")
head(401) and return
end
resource = find_owner_resource(masqueradable_resource)
sign_out(send("current_#{masqueraded_resource_name}"))
sign_in(resource)
request.env['devise.skip_trackable'] = nil
go_back(resource, path: after_back_masquerade_path_for(resource))
cleanup_masquerade_owner_session(masqueradable_resource)
end
end
protected
def masquerade_authorize!
head(403) unless masquerade_authorized?
end
def masquerade_authorized?
true
end
def find_masqueradable_resource
GlobalID::Locator.locate_signed(params[Devise.masquerade_param], for: 'masquerade')
end
def find_owner_resource(masqueradable_resource)
skey = session_key(masqueradable_resource, masquerading_guid)
if Devise.masquerade_storage_method_session?
resource_id = session[skey]
masquerading_resource_class.find(resource_id)
else
data = Rails.cache.read(skey)
GlobalID::Locator.locate_signed(data, for: 'masquerade')
end
end
def go_back(user, path:)
if Devise.masquerade_routes_back
redirect_back(fallback_location: path)
else
redirect_to path
end
end
private
def masqueraded_resource_name
Devise.masqueraded_resource_name || masqueraded_resource_class.model_name.param_key
end
def masquerading_resource_class
@masquerading_resource_class ||= begin
unless params[:masquerading_resource_class].blank?
params[:masquerading_resource_class].constantize
else
unless session[session_key_masquerading_resource_class].blank?
session[session_key_masquerading_resource_class].constantize
else
if Devise.masquerading_resource_class_name.present?
Devise.masquerading_resource_class_name.constantize
else
Devise.masquerading_resource_class || resource_class
end
end
end
end
end
def masquerading_resource_name
Devise.masquerading_resource_name || masquerading_resource_class.model_name.param_key
end
def authenticate_scope!
send(:"authenticate_#{masquerading_resource_name}!", force: true)
end
def after_masquerade_path_for(resource)
'/'
end
def after_masquerade_full_path_for(resource)
after_masquerade_path_for(resource)
end
def after_back_masquerade_path_for(resource)
'/'
end
def save_masquerade_owner_session(masqueradable_resource)
guid = SecureRandom.uuid
skey = session_key(masqueradable_resource, guid)
resource_obj = send("current_#{masquerading_resource_name}")
if Devise.masquerade_storage_method_session?
session[skey] = resource_obj.id
else
# skip sharing owner id via session
Rails.cache.write(skey, resource_obj.to_sgid(for: 'masquerade'))
session[skey] = true
end
session[session_key_masquerading_resource_class] = masquerading_resource_class.name
session[session_key_masqueraded_resource_class] = masqueraded_resource_class.name
session[session_key_masquerading_resource_guid] = guid
end
def cleanup_masquerade_owner_session(masqueradable_resource)
skey = session_key(masqueradable_resource, masquerading_guid)
Rails.cache.delete(skey) if Devise.masquerade_storage_method_cache?
session.delete(skey)
session.delete(session_key_masqueraded_resource_class)
session.delete(session_key_masquerading_resource_class)
session.delete(session_key_masquerading_resource_guid)
end
def session_key(masqueradable_resource, guid)
"devise_masquerade_#{masqueraded_resource_name}_#{masqueradable_resource.id}_#{guid}".to_sym
end
def masquerading_current_user
send("current_#{masquerading_resource_name}")
end
def masquerading_guid
session[session_key_masquerading_resource_guid]
end
end