Understanding how to use Pundit

1.3k views Asked by At

I've been trying to wrap my head around the concept of a policy, which seems straightforward enough--a policy is a set of rules by which access to system privileges and resources is determined.

Simple enough.

So, in order for a user within a system to access a list of, say, every other user within that system, they'd require the necessary credentials (e.g., maybe being an administrator or simply being logged in as a registered user). What I'm struggling to understand at even the most basic level is how to actually USE Pundit to accomplish this. As is the case with the documentation for lots of open-source software I've attempted to learn, the Pundit documentation seems to at best allude to how the tool should work but does not provide a full and concrete example of so much as a basic use case. All I'm looking for here is a "hello world" example so I don't spend three or four days piecing together a confused and half-working implementation myself for something so small as this. Furthermore, what examples Pundit does provide only serve to confuse matters worse.

I've basically created a test application that attempts to use Devise (which I seem to understand and be able to use well enough) and Pundit in combination. So far:

  • A user can register with the site.
  • A user can log in and log out.

Now, what I'd like to do--which is something very basic and probably shouldn't have my furrowing by brow so soon--is to restrict the user's access to the users index page based on whether they are logged into the system, and it's here that I'm completely stuck.

What have I tried so far?

  • I've installed Pundit as per the documentation.
  • I've included Pundit in my application_controller.rb
  • I've generated a User policy, which looks like this:

    class UserPolicy < ApplicationPolicy
      class Scope < Scope
        puts "Inside UserPolicy scope."
        attr_reader :user
    
        def initialize(user)
          @user = user
        end
    
        def index
          puts "You've hit the index."
        end
    
        def resolve
          scope
        end
      end
    end
    
    
    class UsersController < ApplicationController
      before_filter :authenticate_user!
      after_action :verify_authorized
      def index
        @users = User.all
        authorize current_user
      end
    end
    

At this point, I'm completely lost as to how to associate the two--the UserPolicy class and UsersController.

I seem to at least be able to print to my console the message "Inside UserPolicy scope." but otherwise just see the following error message in THE browser:

"not allowed to index? this #"

What am I missing and, if nothing else, where is the gap in my own knowledge that's making this and other Rails-related tools so difficult to learn? I'm a professional software engineer (historically, I've been a front-end engineer and have within the last couple of years been working to become a full-stack engineer), but I find myself getting stuck far too often with open-source tools like this one.

2

There are 2 answers

0
Austio On

Pundit is really just plain ruby objects set in a rails like structure.

I think your confusion is trying to authorize the user, think of it more from a resource perspective. You are limiting users access to a certain object via a certain restful action. Scopes limit what they can see, for instance, admin could probably do scope.all, a single user would probably not be allowed or just be able to scope themselves on the users controller.

In my application policy i define helpers to group certain types of users. Here is an example.

class TicketPolicy < ApplicationPolicy
  class Scope < Struct.new(:user, :scope)
     def resolve
       #Allows government to see their organization
       # only allows citizens to see nothing
       if user.government?
         scope.where(:organization_id => user.organization_id)
       else
         scope.where(:id => 0)
       end
     end
  end


  def index?
    is_government
  end

  def show?
    is_government && is_inside_organization(@record)
  end

  def create?
    is_government && is_inside_organization(@record)
  end

  def new?
    is_government && is_inside_organization(@record)
  end

  def update?
    is_government && is_inside_organization(@record)
  end

  def edit?
    if user.employee?
      return is_mine(@record) && is_inside_organization(@record)
    end
    is_government && is_inside_organization(@record)
  end

  def destroy?
    false
  end
end

This limits the access to the groups/roles and then in my controller i just call

authorize @thing

Then pundit will take care of the access based on the policy.

0
fylooi On

Your implementation has a few issues:

  1. Action authorization (eg. index?) goes into the parent UserPolicy class, not the internal Scope class.
  2. The result of the authorization call needs to return true or false. Your index? call returns puts "You've hit the index.", which evaluates to nil
  3. Controlling access based on whether the user is logged in is authentication, not authorization. Pundit should only take over after the user has been authenticated.