Support for Warden, and thus Devise constraints?
stevenharman opened this issue · comments
Hello, and thank you for the effort you've put into this gem. I am trying to integrate pretender
into our app but have hit a snag. Namely, we use Devise and also leverage Warden route Constraints. That is rather than the Rails Controller-based before_action :authenticate_user!
mechanism, we use a custom constraint, like this in routes.rb
constraints(OrganizationOwnerConstraint.new) do
# define resources here
end
# organization_owner_constraint.rb
class OrganizationOwnerConstraint
def matches?(request)
warden(request).authenticated?(:user) &&
warden(request).user(:user).owner?
end
private
def warden(request)
request.env['warden']
end
end
When Warden runs that constraint it doesn't leverage the current_user
method that pretender
has overwritten on the controller. Thus, so far as the constraint, and Warden, which backs Devise, is concerned, we're not impersonating the other user.
I assume this is not something currently supported by pretender
, yes? Is there any interest in supporting Warden? I would image it will require some additional work to do things like:
# during impersonation...
if warden.authenticated?(:user)
warden.session(scope)[:real_user_id] = warden.user(:user).id
warden.logout(:user)
end
warden.session(scope)[:impersonated_user_id] = true_resource.id
warden.set_user(true_resource, scope: :user)
And then when checking the overridden impersonated_method
(e.g., current_user
) we'd need to check warden for an :impersonated_user_id
and set the @impersonated_user_id
with it. There would be a bit of work to log out the impersonated user and reset Warden when we stop_impersonating_user
as well.
Thoughts?
Hey @stevenharman, the impersonation is a bit tricky to get right, so no plans to do anything special with Warden right now.
OK, that's fair.
Though... I was just able to roll this myself w/o too much trouble. It's dependent on having Warden as the backing authentication mechanism, so there's that.
# application_controller.rb
class ApplicationController < ActionController::Base
#other stuff... including an `impersonating?` method
private
def true_account
if session[:impersonated_account_id].present?
Account.find(session[:real_account_id]) if session[:real_account_id].present?
else
current_account
end
end
helper_method(:true_account)
end
# admin/impersonations_controller.rb
module Admin
class ImpersonationsController < AdminController
def create
imposter = Account.find(params.fetch(:account_id))
real_account = warden.user(:account)
if real_account == imposter
redirect_to(root_path, alert: 'You are already impersonating this user.') && return
end
save_real_account_for_later(real_account)
impersonate_account(imposter)
redirect_to root_path
end
def destroy
imposter = stop_impersonating_account
restore_real_account
redirect_to admin_restaurant_restaurant_employees_path(imposter.user.restaurant), status: 303
end
private
def impersonate_account(imposter)
session[:impersonated_account_id] = imposter.id
warden.set_user(imposter, scope: :account)
end
def restore_real_account
real_account_id = session.delete(:real_account_id)
return unless real_account_id.present?
real_account = Account.find(real_account_id)
warden.set_user(real_account, scope: :account)
end
def save_real_account_for_later(account)
return unless account.present?
session[:real_account_id] = account.id if session[:impersonated_account_id].nil?
warden.logout(:account)
end
def stop_impersonating_account
session.delete(:impersonated_account_id)
imposter = warden.user(:account)
warden.logout(:account)
imposter
end
end
end
If I had a month of Sundays, I might even publish that as a Gem. But who wants that headache! #AMIRITE 😉
@stevenharman I'm going to play around with your snippet to support my #23 use case. I'm using warden hooks for action cable authentication so your added warden user management might be just what I need.
@hwhelchel Nice! I'll try to move this all to a gist at the very least, including the other bits I've added/tweaked since posting the original snippet.
@hwhelchel do you have any example app using this code? I really need to implement this functionality to make it work with action cable, but I've been struggling trying to adapt my code to your example.