Search code examples
ruby-on-railsdeviseactiveadminpundit

Active Admin, Devise and Pundit (Pundit::PolicyScopingNotPerformedError)


I have an existing Rails app with Devise authenticating the User model and Pundit authenticating against an Enrollment model which links User to my Company model. Both User and Company are in the public schema of the apartment gem. I don't suspect apartment is part of the issue but I figured I would mention it.

I added Active Admin with the AdminUser class - I want to keep my admin user separate from the app users.

If I try to access /admin or /admin/dashboard I get:

Pundit::PolicyScopingNotPerformedError at /admin/users
Pundit::PolicyScopingNotPerformedError

If I try my models like /admin/users Pundit seems to ignore the active_admin policies and goes to the main app policies. In my case the app throws an exception because it's expecting an Enrollment vs the AdminUser.

If I disable:

##/config/initializers/active_admin.rb
  config.authorization_adapter = ActiveAdmin::PunditAdapter

##/controllers/application_controller
  after_action :verify_authorized, except: [:landing, :dashboard], unless: :devise_controller?
  after_action :verify_policy_scoped, only: [:index]

It all works but then I lose Pundit etc in my main app.

Here is a gist of my code:

https://gist.github.com/jasper502/4b2f1b8b6f21a26c64a5

Here are the related posts that could find on this issue:

https://gorails.com/forum/using-pundit-with-activeadmin

How to get Active Admin to work with Pundit after login

I was looking to disable Pundit all together over in this post (Can you disable Pundit with Devise and Active Admin?) but it would be nice to just make this work.

UPDATE

I have work around but I still don't know if this should work out of the box and I have some weird issue causing all of this. Gist updated.

I ended up using:

https://viget.com/extend/8-insanely-useful-activeadmin-customizations

and a bit of:

Documentation for conditional before_action/before_filter

and a bit of the answer below. I shoehorned in a filter to force AA to call authorize on the resources and collections inside AA. Next would be to add the policy scopes but my brain hurts too much now.

I also had to add another filter to bypass authentication on the Dashboard as it's headless. Seems to work so far.

UPDATE 2

Hmmm... I think I spoke too soon. This all works only if I am logged in as a regular User - I if I log out it all falls apart again.


Solution

  • @dan-tappin I imagine you already found a similar solution based on your comments but here is what I ended up adding to each of my AA model registrations:

    #app/admin/user.rb
    ActiveAdmin.register User do
      controller do
        before_filter :authorize_index, only: :index
        def authorize_index
          policy_scope(User)
        end
    
        before_filter :authorize_show_edit_destroy, only: [:show, :edit, :destroy]
        def authorize_show_edit_destroy
          authorize resource
        end
      end
    end
    

    Basically this utilizes the ability to execute in the controller scope using normal rails before_filter syntax to limit the execution with :only. Then because the before_filter happens after the inherrited_resources filters we have access to the "resource" and we can authorize against it like you would normally against any model instance. See: https://github.com/activeadmin/activeadmin/issues/1108#issuecomment-14711733

    The reason the policy scope is needed in the first place is because a normal pundit installation requires the following in the application_controller.rb

    #app/controllers/application_controller.rb:
    class ApplicationController < ActionController::Base
      # Prevent CSRF attacks by raising an exception.
      # For APIs, you may want to use :null_session instead.
      include Pundit
      protect_from_forgery with: :exception
    
      #before_action :authenticate_is_admin!
    
      after_action :verify_authorized, except: [:index, :dashboard], unless: :devise_controller?
      after_action :verify_policy_scoped, only: :index, unless: :devise_controller?
    
      rescue_from Pundit::NotAuthorizedError, with: :user_not_authorized
    
      private
      def authenticate_admin!
        redirect_to new_user_session_path unless current_user.admin?
      end
    
      private
      def pundit_user
        current_user
      end
    
      private
      def user_not_authorized
        flash[:error] = "You are not authorized to perform this action."
        redirect_to(request.referrer || new_user_session_path)
      end
    end
    

    It expects a call to policy scope the model for all index actions. Dashboard controller renders the index action by default thus necessitating this before_filter hack.