Giter Club home page Giter Club logo

pretender's Introduction

Pretender

As an admin, there are times you want to see exactly what another user sees. Meet Pretender.

  • Easily switch between users
  • Minimal code changes
  • Plays nicely with Action Cable and auditing tools

💥 Rock on

Pretender is flexible and lightweight - less than 100 lines of code :-)

Works with any authentication system - Devise, Authlogic, and Sorcery to name a few.

🍊 Battle-tested at Instacart

Build Status

Installation

Add this line to your application’s Gemfile:

gem "pretender"

And add this to your ApplicationController:

class ApplicationController < ActionController::Base
  impersonates :user
end

How It Works

Sign in as another user with:

impersonate_user(user)

The current_user method now returns the impersonated user.

You can access the true user with:

true_user

And stop impersonating with:

stop_impersonating_user

Sample Implementation

Create a controller

class UsersController < ApplicationController
  before_action :require_admin! # your authorization method

  def index
    @users = User.order(:id)
  end

  def impersonate
    user = User.find(params[:id])
    impersonate_user(user)
    redirect_to root_path
  end

  def stop_impersonating
    stop_impersonating_user
    redirect_to root_path
  end
end

Add routes

resources :users, only: [:index] do
  post :impersonate, on: :member
  post :stop_impersonating, on: :collection
end

Create an index view

<ul>
  <% @users.each do |user| %>
    <li>Sign in as <%= button_to user.name, impersonate_user_path(user), data: {turbo: false} %></li>
  <% end %>
</ul>

And show when someone is signed in as another user in your application layout

<% if current_user != true_user %>
  You (<%= true_user.name %>) are signed in as <%= current_user.name %>
  <%= button_to "Back to admin", stop_impersonating_users_path, data: {turbo: false} %>
<% end %>

Audits

If you keep audit logs with a library like Audited, make sure it uses the true user.

Audited.current_user_method = :true_user

Action Cable

And add this to your ApplicationCable::Connection:

module ApplicationCable
  class Connection < ActionCable::Connection::Base
    identified_by :current_user, :true_user
    impersonates :user

    def connect
      self.current_user = find_verified_user
      reject_unauthorized_connection unless current_user
    end

    private

    def find_verified_user
      env["warden"].user # for Devise
    end
  end
end

The current_user method now returns the impersonated user in channels.

Configuration

Pretender is super flexible. You can change the names of methods and even impersonate multiple roles at the same time. Here’s the default configuration.

impersonates :user,
             method: :current_user,
             with: ->(id) { User.find_by(id: id) }

Mold it to fit your application.

impersonates :account,
             method: :authenticated_account,
             with: ->(id) { EnterpriseAccount.find_by(id: id) }

This creates three methods:

true_account
impersonate_account
stop_impersonating_account

History

View the changelog

Contributing

Everyone is encouraged to help improve this project. Here are a few ways you can help:

To get started with development:

git clone https://github.com/ankane/pretender.git
cd pretender
bundle install
bundle exec rake test

pretender's People

Contributors

ankane avatar aried3r avatar atul9 avatar cyberk avatar hakanensari avatar hzchirs avatar jschwindt avatar prsimp avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

pretender's Issues

Auto logout user

Hi there,
I wanted to ask whether the functionality to auto log-out pretended user should be added in gem. Currently, I am thinking to achieve it through devise timeoutable module for my case.

Thanks :)

Multiple loggable classes

I have two loggable classes in my app: User and Customer and I'm willing to log into a Customer from a User (which is my admin role). Is there any setup that allows me to do this?
I did check the impersonates method implementation and the #true_scope method isn't as flexible as #current_scope, which may block the impersonate that I described above

If there is no way to do what I'm trying to, what do you guys think about making the #true_scope configurable?

Missing Before_Filter method in README

The README.md, "Sample Implementation" section, "Create a controller" example should either inform the developer to implement their own require_admin! filter or show one in the UsersController example.

Following the documentation but cannot get it to work

I've installed Pretender (0.2.1) on Rails 5.1.

If you see the commented out lines in the method below, the passed user id doesn't get assigned to current_user.

In users_controller.rb

def impersonate
  # current_user.id => 20
  user = User.find(params[:id])
  # user.id => 300
  impersonate_user(user)
  # current_user.id => 20
  ...

in application_controller.rb

impersonates :user,
   method: :current_user,
   with: -> (id) { User.find_by(id: id) }
before_action :current_user

def current_user
  if session[:user_id]
    @current_user ||= User.find(session[:user_id])
  end
end

Working fine in development but not in production with Unicorn

Its working fine in development environment. But in production I get this error:

INFO -- : Refreshing Gem list
NoMethodError: undefined method `impersonates' for ApplicationCable::Connection:Class

In development I'm using puma and in production unicorn, is it possible that this doesn't work with unicorn?

Solutions for use with Doorkeeper / API?

I'm trying to figure out the best way to implement user impersonation with an Ember app and Doorkeeper. Has anyone had success using pretender with Doorkeeper?

One thought I've had: Switch Rails session store from cookie_store to a Redis store so that sessions on the API work, however this violates the principles of REST since context is maintained on the server across requests.

Would love to hear potential solutions!

current_user helper

When using pretender and devise, I noticed that the controller method for current_user is being updated to the impersonated user. However, when calling current_user within a view via <%= current_user %>, I see the true user instead. As a workaround, I updated the view to use <%= controller.current_user %>. Is this expected?

Log in as different Model

Is it possible for this gem to be used with different models? For instance, I want an AdminUser model to be able to login as any User. It works great when I set it up User to User, but not when I use the AdminUser to User.

Help keeping current_user correct

I'm having the same issue as this issue from earlier in the year.

#15

I have a rudimentary user session based on a session cookie. Essentially, my current_user function looks like this

def current_user @current_user ||= User.find_by(id: session[:user_id]) end

In my user controller the redirect sets current_user correctly, but in debugging by the time I get back to the page, current_user is now equal to true_user. Yet, my CanCan abilities are correct for the impersonated user, so something is correct.

This isnt a bug, but since there isnt a forum for this gem I'd like to open a discussion about this. I'm sure its something simple I'm just not seeing.

TIA

ActionCable Integration

I have a Rails 5 app that uses Pretender for impersonation.

Currently when impersonating an account, my action cable channels no longer work. What's the simplest cleanest way to add Pretender to action cable?

To authenticate my cable connection I use:

module ApplicationCable
  class Connection < ActionCable::Connection::Base
    identified_by :current_account

    def connect
      self.current_account = find_verified_account
    end

    private

    def find_verified_account
      if verified_account && cookies.signed['account_expires_at'] > Time.current
        verified_account
      else
        reject_unauthorized_connection
      end
    end

    def verified_account
      @verified_account ||= Account.find_by(id: cookies.signed[:account_id])
    end
  end
end

and here are the warden hooks which cookie the account_id

Warden::Manager.after_set_user do |account, auth, opts|
  scope = opts[:scope]

  auth.cookies.signed["#{scope}_id"] = account.id
  auth.cookies.signed["#{scope}_expires_at"] = 30.minutes.from_now
end

Warden::Manager.before_logout do |_account, auth, opts|
  scope = opts[:scope]

  auth.cookies.signed["#{scope}_id"] = nil
  auth.cookies.signed["#{scope}_expires_at"] = nil
end

I tried this naive solution which didn't work:

module ApplicationCable
  class Connection < ActionCable::Connection::Base
    identified_by :current_account
    extend Pretender
    impersonates :account

    def connect
      self.current_account = find_verified_account
    end

    private

    def find_verified_account
      if verified_account && cookies.signed['account_expires_at'] > Time.current
        verified_account
      else
        reject_unauthorized_connection
      end
    end

    def verified_account
      @verified_account ||= Account.find_by(id: cookies.signed[:account_id])
    end
  end
end

Sorcery + Pretender wont stop impersonating

Hey! I love the gem, but unfortunately when hit the "stop_impersonating" method in my users controller and refresh the page it goes right back. The problem does go away on a full logout.

Here is my controller method:

  def stop_impersonating
    stop_impersonating_user
    redirect_to users_path
  end

And here is a gif of the problem:
http://i.imgur.com/aCYeDpC.gifv

Versions:
rails (= 4.2.4)
pretender (0.2.1)
sorcery (0.9.1)

Let me know if there is any other information I can provide! Thank you for an awesome gem!

current_user reverts back to true_user after redirect

I love the idea and simplicity of this gem but having difficulty getting it to work with my app. Immediately after calling the impersonate_user method in my app, current_user and true_user are set to the correct users. However, once my app hits the controller specified in the redirection after calling impersonate_user, current_user reverts back to being the same as true_user. session[:impersonated_user_id] is still correct (the impersonated user’s id), but @impersonated_user for some reason reverts back to true_user. I put in breakpoints and used .source_location in each case to confirm that the current_user that’s being called is pretender’s current_user. Anyone seen this before or know what might be the cause, or a hint on how to debug it?

Support for Warden, and thus Devise constraints?

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?

not working with devise scope

steps

  1. impersonate_user(normal_user)

  2. put debugger in routes.rb
    authenticate :user, lambda { |u| debugger; u.admin? } do
    mount Sidekiq::Web => '/sidekiq'
    root to: "users#index", as: :admin_root
    end

     authenticate :user, lambda { |u| u.normal_user? } do
       root 'activities#index', as: :authenticated_root
     end
    

Result => u is admin user. all root url are wrong hence. cancancan raises multiple redirect. because of below in applicaiton controller

rescue_from CanCan::AccessDenied do |exception|
    redirect_to(main_app.root_url, :alert => exception.message)
end

Looking for a way to use this with graqpql_devise

I am currently using the following stack for auth flow.

I have an admin user that needs to be able to impersonate a normal user.
I'm having trouble putting this into the code because this seems very tied to the controllers.

I was wondering if anyone has any idea about implementation on this.

gem "devise"
gem "graphql_devise"
gem "active_model_otp", "~> 2.0"
gem "pretender"

current_user does not change after signing out user, signing in different user

I‘m using pretender 0.3.1, rails 5.1.3 and devise 4.3.0. In one before_action I log out the current user and log in a different user. I am not impersonating anyone.

Unfortunately, current_user does not change, even though true_user does:

before_user = current_user
sign_out(current_user)
# at this point I expect current_user to be nil, but it is still `before_user`
sign_in(another_user)
# at this point I expect current_user to be `another_user`, but it is still `before_user`

From a quick glance at the code I think that pretender caches current_user in an instance variable, causing this unexpected behaviour.

impersonate user redirect me to user sign in page

i logged in as admin and try to sign in as user by click button that goes to impersonate method in users controller but it redirect me to users sign in page .i did following thing

  1. define these routes in users
    get :impersonate, on: :member
    get :stop_impersonating, on: :collection

  2. define following line in application controller
    impersonates :user

  3. define follwoing methods in users controller
    def impersonate
    user = User.find(params[:id])
    impersonate_user(user)
    redirect_to dashboard_user_user_path(user)
    end

def stop_impersonating
stop_impersonating_user
redirect_to dashboard_user_user_path(user)
end

Continuous integration

I have not seen any continuous integration configured in this project. Is there a plan to include this as part of the development cycle?

Thanks in advance

Issue with forgery protection and Rails 3.2

Hey @ankane, just a quick issue where I wanted to say thanks for this Gem...I just found it useful for an app I'm working on. Also, I wanted to suggest a README update specific to Rails 3.2.

I had to add an extra line above my controller methods

  skip_before_filter :verify_authenticity_token, only: [:impersonate, :stop_impersonating]

Feel free to drop this line into your README where it makes the most sense. Basically, for Rails 3.2 you have to skip the forgery protection before filters while switching users.

Support multiple scopes

Thanks for maintaining a great gem.

I've come across the same limitation as a few other people: I have an Administrator scope and a Customer scope. I want administrators be able to impersonate customers, without being logged in as a customer.

This has been requested before...

This line is the culprit...

raise Pretender::Error, "Must be logged in to impersonate" unless send(true_method)

It appears the purpose of this is to clear out impersonations from logged-out super users.

# only fetch impersonation if user is logged in
# this is a safety check (once per request) so
# if a user logs out without session being destroyed
# or stop_impersonating_user being called,
# we can stop the impersonation
if send(true_method)
impersonated_resource = impersonate_with.call(request.session[session_key])
instance_variable_set(impersonated_var, impersonated_resource) if impersonated_resource
else

This is a nice sanity check, but it would be great if we could pass in our own method to check the login status of the super user.

Would you be open to a PR that adds a new option to the impersonates call so we could provide our own logged in check?

Something like...

impersonates :customer, ensuring: :administrator_signed_in?

This would solve my issue (and the others listed) with minimal change to the surface area and no change to the behaviour.

If this appeals... I'm happy to put something together.

And, if so... do you have a preference for the option name?

  • ensuring: :administrator_signed_in?
  • log_in_guard: :administrator_signed_in?
  • sudo: :administrator_signed_in?
  • when: :administrator_signed_in?
  • if: :administrator_signed_in?

Hooking into devise 2.0.5 and rails 3.2

I've had two problems with hooking pretender into our application.

First, just putting the impersonates :user call into our ApplicationController did not work. We got 'undefined method "current_user"' when we did so. That's because Devise hasn't actually defined that method yet; it's defined when the ActiveSupport.on_load hook is called for ApplicationController, which is later in the Rails startup lifecycle. We've worked around that using:

def current_user
    super
end

Secondly, the Very Important thing (calling stop_impersonating_user before logout) appears to be both more difficult than documented, and unnecessary in our environment. We're using cookie-based (not database-backed) sessions.

Devise provides its own Devise::SessionsController; subclassing it is possible, but requires bringing its views into our own application (note also that it does not provide a logout method; instead, it has a destroy).

Since the session appears to be completely destroyed by Devise's destroy method (and Warden's session handling), I'm wondering what risks might be posed by not calling stop_impersonating_user.

Less directly, we looked at hooking at Warden's before_logout callback, but that does not provide the controller object, so makes direct use of stop_impersonating_user impossible.

We've not encountered the "Security warning" exception in practice while testing.

Am I taking any risks by not calling stop_impersonating_user before logout?

Pretender 0.3 causes rails app to crash

I wish I could provide more information as to what the issue is, but after updating our gems, our app stopped worked. Downgrading pretender back to 0.2 fixed the issue. The only error logged is as follows

 F, [2017-06-14T14:29:44.599534 #15416] FATAL -- : [55a6371f-bce8-4f0c-9346-901a223612a3]
 F, [2017-06-14T14:29:44.599708 #15416] FATAL -- : [55a6371f-bce8-4f0c-9346-901a223612a3] SystemStackError (stack level too deep):
 F, [2017-06-14T14:29:44.599805 #15416] FATAL -- : [55a6371f-bce8-4f0c-9346-901a223612a3]
 F, [2017-06-14T14:29:44.599892 #15416] FATAL -- : [55a6371f-bce8-4f0c-9346-901a223612a3] app/controllers/application_controller.rb:27:in `block in <class:ApplicationController>'

The line referenced if a very simple current_user if

 if current_user && current_user.is_admin

Context of `with` proc

At the moment the context of self from within the with proc will be the class in which the impersonates function is called (ie your ApplicationController). Instead of using:

impersonate_with.call(session[session_key])

I'd suggest instead to use

self.instance_exec(session[session_key], &impersonate_with)

which will give the with proc access to everything in the current controller instance.

I'd be happy to put together a PR if it's something you'd be interested in.

Working in development, Not working in production

Deployment is on Heroku. Nothing I can notice in production.rb would cause this issue and
there's no production specific gem that would cause this either.

Any ideas or is this a problem anyone else has experienced before?

Multilevel impersonation

I would like to know if Pretender supports multilevel impersonation, that is, user A impersonates user B, and while being impersonated, user B (which is actually user A) impersonates user C.

Thanks

Adding current_user to ApplicationController for IDE detection breaks impersonate_user

Hey, first of all, thanks for this gem. We've been using it succesfully for almost 2 years now without fail.

I don't think this is a bug of Pretender really, but maybe you can help me. RubyMine doesn't understand current_user when using Devise, so I've put a function placement in my ApplicationController according to this solution:

class ApplicationController < ActionController::Base
  # @return [User]
  def current_user
    super
  end
end

When using this, all the red squiggly lines disappear and the IDE understands it's working with a User, making suggestions better. Unfortunately the impersonate_user function stops working because of this.

I've had a look at your code and my guess would be that I'm overwriting your define_method impersonated_method. WDYT?

Destroying the session?

Is there a reason this gem destroys the user's session when stopping the impersonation?

request.session.delete(session_key)

Seems like you should be able to stop the impersonation (of another user), and just revert back to the true user?

User and admin role model

Hi @ankane, i have two separate models Admin and User and i want to impersonate the User by an Admin. I also followed the with part in configuration but nothing happened. Please guide me what actually i should do to achieve this task. Thanks!

current_user equals true_user after override

I'm having a issue related to #31

When we override current_user:

def ApplicationController < ActionController::Base
  impersonates :user

  def current_user
    super.decorate
  end
end

The super call not return the current impersonated user but the true_user there's a way to bypass this?

Rails API support

When try access my API, I get the follow error:

ActionController::RoutingError (undefined method `impersonates' for ApplicationController:Class):

My controller extends to ActionController::API:

class ApplicationController < ActionController::API
  include AuthenticationConcern
  include AuthorizationConcern

  impersonates :user
end

Looking at the gem code, I see that extend from ActionController :: Base.

Pretender does not work correctly with cells gem

Hello!

We're using this gem for a while and it is so helpful for us but, currently I am facing an issue. We implemented cells to our project (you can see the gem here: https://github.com/trailblazer/cells) and pretender does not work inside of cells. Cells get current_user (thanks to devise gem) as the real user, not as the user that I want to pretend.

I can pass the current_user to the cell as options but this is not the way I want to solve it because I have lots of cells. I am looking for a solution that I can implement into a class that I call BaseCell. I am inheriting BaseCell into my Cells.

Is there anyone who is facing a similar issue or do you have any suggestions for me?

true user reset after UI refresh

I implemented the pretender gem on my app in order to use impersonations .
Im using Devise auth and the issue is :
after impersonating the current_user and true_user are set correctly, but once i refresh the application , true_user is reset to be similar as the current_user (the impersonated one ).

thanks :)

Support jwt storage strategy

Hi! I was trying to integrate pretender as a POC for our app.

We use Devise and have session_storage disabled.

Pretender makes mandatory use of sessions.

Did anyone think of an implementation that is compatible with jwt storage or something like that ?

Loosing true_user

We seem to be experiencing an issue where (quite magically) the true_user is lost and our admin staff suddenly just "become" the user. Perhaps the session timing out?

Any ideas what could be causing this?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.