1

I'm trying to integrate pundit with my active admin and devise configuration. But the app works weirdly. It takes in model/record as current_user.

my policy file:

class AdminUserPolicy
  attr_reader :current_user, :model

  def initialize(current_user, model)
    Rails.logger.info '--------------- initialize called-------------------'
    Rails.logger.info current_user
    Rails.logger.info model
    @current_user = current_user
    @record = model
  end

  def index?
    @current_user.admin?
  end
end

controller:

controller do
    include Pundit
    protect_from_forgery
    rescue_from Pundit::NotAuthorizedError, with: :user_not_authorized
    before_action :authenticate_admin_user!

    def index
        authorize current_admin_user
        super
    end

    private

        def user_not_authorized
           flash[:alert]="Access denied"
           redirect_to (request.referrer || admin_root_path)
        end
end

The log is as follows:

--------------- initialize called----------------------------------------

#<AdminUser:0x007f27733f8a80>
Completed 500 Internal Server Error in 364ms (ActiveRecord: 312.8ms)



NoMethodError (undefined method `admin?' for nil:NilClass):

app/policies/admin_user_policy.rb:13:in `index?'
app/admin/dashboard.rb:19:in `index'
  Rendering /usr/local/rvm/gems/ruby-2.3.4/gems/actionpack-5.0.6/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb within rescues/layout
  Rendering /usr/local/rvm/gems/ruby-2.3.4/gems/actionpack-5.0.6/lib/action_dispatch/middleware/templates/rescues/_source.html.erb
  Rendered /usr/local/rvm/gems/ruby-2.3.4/gems/actionpack-5.0.6/lib/action_dispatch/middleware/templates/rescues/_source.html.erb (4.8ms)
  Rendering /usr/local/rvm/gems/ruby-2.3.4/gems/actionpack-5.0.6/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb
  Rendered /usr/local/rvm/gems/ruby-2.3.4/gems/actionpack-5.0.6/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb (2.5ms)
  Rendering /usr/local/rvm/gems/ruby-2.3.4/gems/actionpack-5.0.6/lib/action_dispatch/middleware/templates/rescues/_request_and_response.html.erb
  Rendered /usr/local/rvm/gems/ruby-2.3.4/gems/actionpack-5.0.6/lib/action_dispatch/middleware/templates/rescues/_request_and_response.html.erb (1.1ms)
  Rendered /usr/local/rvm/gems/ruby-2.3.4/gems/actionpack-5.0.6/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb within rescues/layout (39.5ms)

The weird part is, according to logs, current_user=nil and model=#<AdminUser:0x007f27733f8a80>

I swapped current_user with model my policy file as

def index?
  @record.admin?
end

And it works! I don't understand this strange behaviour.

Jesse Sravya
  • 121
  • 1
  • 2
  • 9

1 Answers1

1

Pundit policy doc says that it calls the current_user method to retrieve what to send into the first argument of the initialize method inside the Policy class. If you have configured ActiveAdmin to retrieve the current logged in user by using current_admin_user, then you have to override the pundit default method in your ApplicationController class like so: Ref

class ApplicationController < ActionController::Base
  // ...
  def pundit_user
    current_admin_user // or whatever based on ActiveAdmin initializer config
  end
end

In order to make the defined policy working, you have to invoke authorize inside the controller action with the instance of the corresponding policy model. So if you have a PostPolicy and you want to authorize the update action, you have to do the following:

controller do
  def update
    @post = Post.find(params[:id])
    authorize @post // the current user will be automatically sent to the PostPolicy
    super
  end
end

The authorize method automatically infers that Post will have a matching PostPolicy class, and instantiates this class, handing in the current user and the given record. It then infers from the action name, that it should call update? on this instance of the policy. In this case, you can imagine that authorize would have done something like this:

unless PostPolicy.new(current_user, @post).update?
  raise Pundit::NotAuthorizedError, "not allowed to update? this #{@post.inspect}"
end

Having all these, in your case, if you want that a user should be authorized before viewing the list of all users, you can define the AdminUserPolicy like you have done already. Then in the index action of your AdminUserController,

controller do
  def index
    @users = AdminUser.all
    authorize @users // NOT `authorize current_admin_user`
    super
  end
end

You can pass a second argument to authorize if the name of the permission you want to check doesn't match the action name. For example:

def publish
  @post = Post.find(params[:id])
  authorize @post, :update?
  @post.publish!
  redirect_to @post
end
Wasif Hossain
  • 3,900
  • 1
  • 18
  • 20
  • Thank you so much! That clears a lot of things. Queries like ```@users = Admin.all``` and ```@post=Post.find()``` would add to unnecessary processing, as my use case is only knowing if a user can or cannot use the methods like index, update etc. Is there a way to redirect them to spectific policy? Or can I continue using current_admin_user? – Jesse Sravya Nov 06 '17 at 18:25
  • glad to know that! would you pls elaborate what do you mean by `Is there a way to redirect them to specific policy?` – Wasif Hossain Nov 06 '17 at 18:26
  • I just want to know "can this user view" or "can this user edit". I'm not concerned with @post variables. So querying them would be unnecessary. But if I omit them, it wouldn't know which policy to check in. So how do I just say "authorize :index?" without any input. – Jesse Sravya Nov 06 '17 at 19:27
  • you can provide the class instead of an instance in the first argument of `authorize` method, e.g. `authorize Post`. But you must have to use some policy object in the first argument; be it an instance, a class or just a symbol (Headless Policy: https://github.com/elabs/pundit#headless-policies) – Wasif Hossain Nov 06 '17 at 19:34
  • i see. you have another question on cancancan posted very recently here: https://stackoverflow.com/questions/47095147/unable-to-use-cancancan-with-activeadmin-and-devise-undefined-method-dashboards#47095147. should I answer that too? – Wasif Hossain Nov 06 '17 at 19:37
  • 1
    Thank you so much! That'll work just perfect. I chose pundit over cancancan, as I found it troublesome. I'm curious to know though. – Jesse Sravya Nov 06 '17 at 19:39
  • actually i found `cancancan` to be more minimalistic than `pundit` while working with ActiveAdmin :) With the first one, I was able to setup authorization with less than 15 lines of code, whereas pundit would require at least all the boilerplate policies and then invoking `authorize` in every action. AA has optimized this task for cancancan in a cool way, so I really enjoyed it – Wasif Hossain Nov 06 '17 at 19:43
  • When I observed the logs, CanCanCan's function calls were greater than those of pundit. – Jesse Sravya Nov 06 '17 at 19:56
  • just answered your other question on cancancan: https://stackoverflow.com/a/47144739/1977104 – Wasif Hossain Nov 06 '17 at 19:56