ankane / pretender

Log in as another user in Rails

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

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.