Pundit for authorization with Rspec Rails

Authorization is one of the important feature of any web app.
With rails you can leverage the power of all those wonderful open source gems that are available to you or you can code your own authorization module.

The most commonly used gems for authorization are Pundit and Cancan (now cancancan, since the community took over the development of the gem. One reason for which I love the Ruby community the most :))

Now since we have those 2 popular gem, the next big question is which to chose from

Pundit or Cancan ?

Cancan

  • Popular among the two.
  • More star gazers in github.
  • Now being maintained by the community.
  • Custom DSL

Pundit

  • Plain ruby classes and Object Oriented code design patterns.
  • No DSL to master
  • PORO.

Basic usage

The setup and install instructions are documented in detailed in the gem's wiki itself.
The main principle is to extract the authorization rules into policy files, which are POROs:

For eg:, an Article plolicy might look like:

class ArticlePolicy
  attr_reader :user, :article

  def initialize(user, article)
    @user = user
    @article = article
  end

  def new?
    user.has_roles?('author')
  end

  alias_method :create?, :new?

  def edit?
    user.has_roles?('author') && article.is_draft?
  end

  alias_method :update?, :edit?
end

And your corresponding controller will be something like :

class ArticleController < ApplicationController
  include Pundit

  rescue_from Pundit::NotAuthorizedError, with: :user_not_authorized

  ...

  def edit
    @article = Article.find(params[:id])
    authorize @article
    ...
  end

  ...

  private

  def user_not_authorized
    flash[:error] = 'You are not authorized to perform this action.'
    redirect_to(request.referrer || root_path)
  end
end

Testing with Rspec.

Pundit works well with rspec. Thunderboltlabs has a nice article written on testing pundit policies with rspec.

Going ahead and adding the matchers for pundit:

RSpec::Matchers.define :permit do |action|
  match do |policy|
    policy.public_send("#{action}?")
  end

  failure_message_for_should do |policy|
    "#{policy.class} does not permit #{action} on #{policy.record} for #{policy.user.inspect}."
  end

  failure_message_for_should_not do |policy|
    "#{policy.class} does not forbid #{action} on #{policy.record} for #{policy.user.inspect}."
  end
end

to spec/pundit_matcher.rb and including that in our spec_helper using Dir[Rails.root.join("spec/support/**/*.rb")].each {|f| require f}.

Once we have the custom matchers and the support files loaded into spec helper we can go ahead and write our test cases our article policy.

So our rspec test will be having a following pattern:

require 'spec_helper'

describe ArticlePolicy do
  subject { ArticlePolicy.new(user, article) }

  let(:article) { FactoryGirl.create(:article) }

  context 'for a visitor' do
    let(:user) { nil }

    it { should_not permit(:create)  }
    it { should_not permit(:new)     }
    it { should_not permit(:update)  }
    it { should_not permit(:edit)    }
  end

  context "for an author" do
    let(:user) { FactoryGirl.create(:user, role: 'author') }

    it { should permit(:create)  }
    it { should permit(:new)     }
    it { should permit(:update)  }
    it { should permit(:edit)    }
  end
end

Tweaks with shoulda matchers.

In case your application is using shoulda matchers there is a chance that the above wont work, as a result of conflicting namespace. You can read more about the same here.

So the work around would be to tweak your support file. Instead of using the conflicting permit we just rename it to permitted a little bit to the following:


RSpec::Matchers.define :permitted_to do |action|
  match do |policy|
    policy.public_send("#{action}?")
  end

  failure_message_for_should do |policy|
    "#{policy.class} does not permit #{action} on #{policy.record} for #{policy.user.inspect}."
  end

  failure_message_for_should_not do |policy|
    "#{policy.class} does not forbid #{action} on #{policy.record} for #{policy.user.inspect}."
  end
end

Updating our test case for the above workaround,

require 'spec_helper'

describe ArticlePolicy do
  subject { ArticlePolicy.new(user, article) }

  let(:article) { FactoryGirl.create(:article) }

  context 'for a visitor' do
    let(:user) { nil }

    it { should_not permitted_to(:create)  }
    it { should_not permitted_to(:new)     }
    it { should_not permitted_to(:update)  }
    it { should_not permitted_to(:edit)    }
  end

  context "for an author" do
    let(:user) { FactoryGirl.create(:user, role: 'author') }

    it { should permitted_to(:create)  }
    it { should permitted_to(:new)     }
    it { should permitted_to(:update)  }
    it { should permitted_to(:edit)    }
  end
end

Another possible work around is not to load the shoulda matchers in your policies specs. You can restrict this by loading them only to your models, controllers etc.

Hope you find this helpful.