Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Masquerade with Admin and User models and authenticated routes #72

Open
DanGrenier opened this issue Jan 4, 2021 · 9 comments
Open

Comments

@DanGrenier
Copy link

I have seen similar issues to what I am experiencing but the suggested solutions don't seem to be working.

I have a Rails app with 2 devise models. Admin and User
I also use CanCan for authorization.

The app is set up to use authenticated routes and also authenticate users before accessing routes (instead of using a before_action in a controller to check whether the user is signed in or not)

So the routes for devise are

devise_for :admins, path: 'admins', controllers: 
  {sessions: "admins/sessions",
   registrations: "admins/registrations"}
  
devise_for :users, path: 'users', controllers: 
  {sessions: "users/sessions",
   registrations: "users/registrations",
   masquerades: "admins/masquerades"}

And then the authenticated routes are

authenticated :user do 
  	root to: 'public#userpage', as: "authenticated_user" 
  end

authenticated :admin do 
  	root to: 'public#adminpage', as: "authenticated_admin" 
  end

This just shows a different root (default) page once an Admin or User is signed in. If no one is signed in, it`s a different root page with the login screen etc

Then the regular routes are set up like the following to ensure the user or admin be authenticated before accessing those routes

authenticate :user do  
    resources :users
    resources :franchises, only: [:edit, :update]
    resources :accountants, only: [:show]
    resources :bank_accounts
    resources :credit_cards
end

authenticate :admin do 
    namespace :admins do 
      resources :users
      resources :admins
      resources :franchises
      resources :accountants
   end
end

I have added masquerade to my Gemfile and added masqueradable to the User model.
I also added the following code to the devise.rb in the initializers folder

  config.masquerading_resource_class = Admin
  config.masqueraded_resource_class = User

In my AdminAbility file for CanCan, I added

  can :masquerade, User

Then I created a view on the Admin side of the application that lists the users with a link using the following code, where u is the user object

<%= link_to "Login As", masquerade_path(u),class: "btn btn-sm"%>

When I click the link, it just goes back to the root of the application, and thus, shows me the root page of the user I was already signed in as (The Admin user).

If I add the following to the application.html.erb

<% if user_masquerade? %>
    <div class="alert-warning text-center" style="margin-bottom: 0px">
      You're logged in as Someone Else.
    </div>
<% end %>

The message appears after I click the link, but I still get the admin root page and not the user root page. If I try to access current_user in that div above, I get the undefined method for nil:NilClass error, suggesting that current_user is not initiated.

When I check the application log, I noticed the following (There are a few debug messages that I put in there to make sure the classes and names were correct)

I, [2021-01-04T15:55:44.055460 #3265]  INFO -- : Started GET "/users/masquerade/1?masquerade=hDTkCJBzjkqTxY7uXow7Zw&masqueraded_resource_class=User" for 192.168.5.116 at 2021-01-04 15:55:44 -0500
I, [2021-01-04T15:55:44.060674 #3265]  INFO -- : Processing by Admins::MasqueradesController#show as HTML
I, [2021-01-04T15:55:44.061207 #3265]  INFO -- :   Parameters: {"masquerade"=>"hDTkCJBzjkqTxY7uXow7Zw", "masqueraded_resource_class"=>"User", "id"=>"1"}
D, [2021-01-04T15:55:44.063518 #3265] DEBUG -- :   Admin Load (0.6ms)  SELECT "admins".* FROM "admins" WHERE "admins"."id" = $1 ORDER BY "admins"."id" ASC LIMIT $2  [["id", 1], ["LIMIT", 1]]
D, [2021-01-04T15:55:44.064760 #3265] DEBUG -- :   ↳ app/controllers/application_controller.rb:10:in `current_ability'
D, [2021-01-04T15:55:44.069625 #3265] DEBUG -- :   User Load (1.4ms)  SELECT "users".* FROM "users" WHERE "users"."id" IS NULL AND "users"."id" = $1 ORDER BY "users"."id" ASC LIMIT $2  [["id", 1], ["LIMIT", 1]]
D, [2021-01-04T15:55:44.070722 #3265] DEBUG -- :   ↳ app/controllers/admins/masquerades_controller.rb:3:in `show'
I, [2021-01-04T15:55:44.072528 #3265]  INFO -- : Redirected to http://192.168.5.112/users/sign_in
D, [2021-01-04T15:55:44.073742 #3265] DEBUG -- : Masqueraded Resource Class : User
D, [2021-01-04T15:55:44.074230 #3265] DEBUG -- : Masquerading Resource Class : Admin
D, [2021-01-04T15:55:44.075315 #3265] DEBUG -- : Masquerading Resource Name : admin
D, [2021-01-04T15:55:44.076344 #3265] DEBUG -- : Masquerading Resource Name : user
I, [2021-01-04T15:55:44.076942 #3265]  INFO -- : Completed 302 Found in 15ms (ActiveRecord: 2.0ms | Allocations: 11907)


I, [2021-01-04T15:55:44.091028 #3265]  INFO -- : Started GET "/users/sign_in" for 192.168.5.116 at 2021-01-04 15:55:44 -0500
I, [2021-01-04T15:55:44.096534 #3265]  INFO -- : Processing by Users::SessionsController#new as HTML
D, [2021-01-04T15:55:44.100094 #3265] DEBUG -- :   Admin Load (1.0ms)  SELECT "admins".* FROM "admins" WHERE "admins"."id" = $1 ORDER BY "admins"."id" ASC LIMIT $2  [["id", 1], ["LIMIT", 1]]
D, [2021-01-04T15:55:44.101238 #3265] DEBUG -- :   ↳ app/controllers/application_controller.rb:44:in `prepare_exception_notifier'
I, [2021-01-04T15:55:44.103335 #3265]  INFO -- : Redirected to http://192.168.5.112/
I, [2021-01-04T15:55:44.104375 #3265]  INFO -- : Filter chain halted as :check_user rendered or redirected
I, [2021-01-04T15:55:44.104912 #3265]  INFO -- : Completed 302 Found in 7ms (ActiveRecord: 1.0ms | Allocations: 5153)

So from the looks of it, It tries to access the users/sign_in page in order to sign in as the chosen user but that doesn't seem to be happening and it looks like it's just reloading the Admin user (based on the SQL showing up).

Not sure if it's related to the authenticated routes. If so, is there a way to set that up to work?
If not, what else can I try to get this to behave the way it is intended?
Let me know what I can debug/trace/provide in order to find out what's going on.

Thank you

@PBSITProjects
Copy link

Update.

So I got a little further on the issue
Turns out that I had added some before_action in my sign in controllers recommended by the Devise github repo when using multiple models.

This concern is to make sure that a logged in Admin cannot
#access the user sign in page and vice versa.
#Doing so would mess up the authenticity tokens

module Accessible
  extend ActiveSupport::Concern
  included do
    before_action :check_user
  end

  protected
  #If a user or admin is already logged in and they try to access
  #the sign in page of another user type, we simply redirect
  #them to their corresponding root page 
  def check_user
    if current_user
      flash.clear
      redirect_to(authenticated_user_path) && return
    end
  end
end

And then this concern is used in the different sign in controllers for the Admin user and the regular User.
So based on the concern above, when trying to access the sign in page of the user, it was checking if there was already a user sign in (current_user) and would just redirect_to the root of the authenticated user.

So I added a skip_before_action on the Users SessionsController to make sure it would not do that

class Users::SessionsController < Devise::SessionsController
  include Accessible
  skip_before_action :check_user, only: [:destroy, :create]

  # GET /resource/sign_in
  # def new
  #   super
  # end

  # POST /resource/sign_in
  # def create
  #   super
  # end

  # DELETE /resource/sign_out
  # def destroy
  #   super
  # end

  # protected

  # If you have extra params to permit, append them to the sanitizer.
  # def configure_sign_in_params
  #   devise_parameter_sanitizer.permit(:sign_in, keys: [:attribute])
  # end
end

Now the masquerade process goes further but it now stops at the user sign in and says user not found. It still detects that the masquerading process has been started bassed on the flash message up top in yellow but it can't find the user because based on the application logs, it is still trying to load the Admin user, not the selected user to masquerade.

image

I, [2021-01-04T15:55:44.055460 #3265]  INFO -- : Started GET "/users/masquerade/1?masquerade=hDTkCJBzjkqTxY7uXow7Zw&masqueraded_resource_class=User" for 192.168.5.116 at 2021-01-04 15:55:44 -0500
I, [2021-01-04T15:55:44.055914 #3265]  INFO -- : Cannot render console from 192.168.5.116! Allowed networks: 127.0.0.0/127.255.255.255, ::1
I, [2021-01-04T15:55:44.060674 #3265]  INFO -- : Processing by Admins::MasqueradesController#show as HTML
I, [2021-01-04T15:55:44.061207 #3265]  INFO -- :   Parameters: {"masquerade"=>"hDTkCJBzjkqTxY7uXow7Zw", "masqueraded_resource_class"=>"User", "id"=>"1"}
D, [2021-01-04T15:55:44.063518 #3265] DEBUG -- :   Admin Load (0.6ms)  SELECT "admins".* FROM "admins" WHERE "admins"."id" = $1 ORDER BY "admins"."id" ASC LIMIT $2  [["id", 1], ["LIMIT", 1]]
D, [2021-01-04T15:55:44.064760 #3265] DEBUG -- :   ↳ app/controllers/application_controller.rb:10:in `current_ability'
D, [2021-01-04T15:55:44.069625 #3265] DEBUG -- :   User Load (1.4ms)  SELECT "users".* FROM "users" WHERE "users"."id" IS NULL AND "users"."id" = $1 ORDER BY "users"."id" ASC LIMIT $2  [["id", 1], ["LIMIT", 1]]
D, [2021-01-04T15:55:44.070722 #3265] DEBUG -- :   ↳ app/controllers/admins/masquerades_controller.rb:3:in `show'
I, [2021-01-04T15:55:44.072528 #3265]  INFO -- : Redirected to http://192.168.5.112/users/sign_in
D, [2021-01-04T15:55:44.073742 #3265] DEBUG -- : Masqueraded Resource Class : User
D, [2021-01-04T15:55:44.074230 #3265] DEBUG -- : Masquerading Resource Class : Admin
D, [2021-01-04T15:55:44.075315 #3265] DEBUG -- : Masquerading Resource Name : admin
D, [2021-01-04T15:55:44.076344 #3265] DEBUG -- : Masquerading Resource Name : user
I, [2021-01-04T15:55:44.076942 #3265]  INFO -- : Completed 302 Found in 15ms (ActiveRecord: 2.0ms | Allocations: 11907)


I, [2021-01-04T15:55:44.091028 #3265]  INFO -- : Started GET "/users/sign_in" for 192.168.5.116 at 2021-01-04 15:55:44 -0500
I, [2021-01-04T15:55:44.091896 #3265]  INFO -- : Cannot render console from 192.168.5.116! Allowed networks: 127.0.0.0/127.255.255.255, ::1
I, [2021-01-04T15:55:44.096534 #3265]  INFO -- : Processing by Users::SessionsController#new as HTML
D, [2021-01-04T15:55:44.100094 #3265] DEBUG -- :   Admin Load (1.0ms)  SELECT "admins".* FROM "admins" WHERE "admins"."id" = $1 ORDER BY "admins"."id" ASC LIMIT $2  [["id", 1], ["LIMIT", 1]]
D, [2021-01-04T15:55:44.101238 #3265] DEBUG -- :   ↳ app/controllers/application_controller.rb:44:in `prepare_exception_notifier'
I, [2021-01-04T15:55:44.103335 #3265]  INFO -- : Redirected to http://192.168.5.112/
I, [2021-01-04T15:55:44.104375 #3265]  INFO -- : Filter chain halted as :check_user rendered or redirected
I, [2021-01-04T15:55:44.104912 #3265]  INFO -- : Completed 302 Found in 7ms (ActiveRecord: 1.0ms | Allocations: 5153)

Definitely getting closer. But any advice or other things to try would be appreciated.

@oivoodoo
Copy link
Owner

oivoodoo commented Feb 5, 2021

Hi @PBSITProjects @DanGrenier .

Somehow I missed the issue notification for the gem. Sorry to hear that you have the issues with lib. Have you checked the https://github.com/oivoodoo/devise_masquerade/tree/master/spec/dummy it's having the example application for integration test https://github.com/oivoodoo/devise_masquerade/blob/master/features/multiple_masquerading_models.feature .

if you see any possible differences in usage, please write me back . I will try to cover it by test cases. For now I am going to give a try to research the problem.

@DanGrenier
Copy link
Author

Hi @oivoodoo . Thanks for the reply.

I looked at the dummy application and while it's similar to what I have, It's not quite the same.
I created a tiny app that more or less replicates what I am trying to accomplish.

You can take a look at it here
https://github.com/DanGrenier/masquerade_test

If you seed the database, it will create one Admin and one User.
When you first run the application, you end up on the main landing page (no one signed in).
Then you can sign in as the Admin using the link. Enter username and password.
You will end up on the Admin dashboard (authenticated root route)
You can then select the option to masquerade as user. Select the lone user in the list.
And you will see that nothing happens. We just end up back on the Admin dashboard and we are still logged in as the Admin.

@oivoodoo
Copy link
Owner

oivoodoo commented Feb 10, 2021

Hi @DanGrenier .

it was great debugging using your app. Thank you!

Please give a try 1.3.2 version. I believe it could solve your issue.

828c68e

@DanGrenier
Copy link
Author

Hi @oivoodoo
Thanks for the quick reply.
Everything appears to be working except that the user_masquerade? method does not appear to be working so the Notification that you are currently masquerading and the link to come back to the prior user is not showing either.
But it does sign in and renders the app as the masqueraded user!!

@oivoodoo
Copy link
Owner

Going to make PR for your demo project, yesterday I believe I tested a back button as well. Write you back during the day.

@DanGrenier
Copy link
Author

No rush. I'll try to figure this one out on my own in the meantime.

@DanGrenier
Copy link
Author

OK so I was able to figure this one out.

First, to take care of the user_masquerade? not working and therefore not showing up the div that warns you are masquerading and showing the back path, All I had to do was to enable cache in my development environment per the documentation.

But then I was faced with an authorization issue coming from CanCan.

So in my AdminAbility file I had the following

class AdminAbility
  include CanCan::Ability

  def initialize(user)
      can :masquerade, User
  end
end

In my MasqueradesController, I have the following

class Admins::MasqueradesController < Devise::MasqueradesController
  def show
    super
  end
  
  protected
 
  def masquerade_authorize!
    authorize!(:masquerade, User)
  end
 
end

And then in my ApplicationController, as per CanCan best practice, I set the current_ability instance variable depending on whether the logged-in user is an admin or regular user

class ApplicationController < ActionController::Base
  before_action :masquerade!
  before_action :configure_permitted_params, if: :devise_controller?
  rescue_from CanCan::AccessDenied,with: :access_error 
  
  def current_ability
    @current_ability ||= current_admin ? AdminAbility.new(current_admin) : UserAbility.new(current_user)  
  end

protected 
    
  def configure_permitted_params
    devise_parameter_sanitizer.permit(:account_update, keys: [:email,:password, :current_password, :time_zone])
  end

private

  def access_error(exception)
    flash[:warning] = exception.message
    redirect_to root_path
  end
end

I was able to masquerade as a user. But when clicking on the logout button which performs the masquerade_back action, I was getting a "You are not authorized to access this page" that's coming from CanCan and I was still logged in as the masqueraded user but lost the ability to click on the masquerade_back action.

After I removed the masquerade_authorize! method from my masquerades controller, it worked. So it looks as if when going through the masquerades controller the first time (as an Admin) it works but then when going through it again (as a User) that's where the authorization gets triggered

@jathayde
Copy link

jathayde commented Jul 9, 2022

I'm hitting a similar issue (can start masquerading, but trying to logout returns the not authorized error @DanGrenier referenced. This only started happening after a Rails 7 app update from Rails 6.1.3. Commenting out the masquerade_authorize! method in masquerade_controllers.rb does allow for logout to occur.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants