Canner
Canner is an authorization gem heavily modeled after Pundit. The goal is to take away some of the magic that exists in other auth gems out there and provide the flexibility needed for your specific app.
Who needs another auth gem? There's a bunch of very good ones out there. Pundit, cancan, cancancan, Declarative Authorization to name a few very good alternatives.
Unfortunately none of those solutions had built in support for a requirement I had on a project. My application needed to authorize a user at a given store location.
Suppose I opened a store in Pittsburgh named Joe's Hash Rockets. The word quickly got around that Joe's hash rockets are the best in town! Business is booming and after some market research I determine that Cleveland is a perfect place to open another store location.
The problem is that I have some reports that only the store manager should see. In any of the above listed gems if you have a manager role you could see all the reports regardless of the store location aka store branch.
Canner takes this additional layer of authorization in to account. Instead of saying: Can the currently signed in user access this particular report. Canner can say: Can the currently signed in user access this particular report for this particular branch.
To do this in other libraries you might find yourself creating roles like pittsburgh_manager and cleveland_manager. Or maybe writing some monkey patches to hack in what you need.
Canner is designed to give you an outline of what you'll need for your app's auth needs and leaves the level of complexity needed up to the requirements of your particular application.
Installation
I've only tested Canner on rails 4 but it should work find in rails 3.2 apps.
To install Canner just put the following in your gem file.
gem "canner"
Then run
bundle install
After the gem is installed you'll need to run the install generator:
rails g canner:install
This will create a policies directory and create the base_policy.rb
Now include canner in your application_controller.rb
include Canner
You'll then need to create a Policy class for the models you'd like to authorize.
rails g canner:policy user
Restart your server
Policies
As mentioned Canner is strongly influenced by Pundit and is also based on Policy objects. Your policy objects should be named using the pattern model_namePolicy.rb. i.e UserPolicy, CustomerPolicy, AppPolicy.
Your policy models need to implement 2 methods:
def canner_scope
end
def can?
end
canner_scope
The canner_scope method is used to scope the models consistently in your app.
For example in my app the Customers controller uses the canner_scope to ensure only Users from the current_company are displayed.
class CustomersController < ApplicationController
respond_to :html, :json
before_action :authenticate_user!
def index
@customers = canner_scope(:index, :customer)
can?(:index, :customer)
end
end
and the policy is:
class CustomerPolicy < BasePolicy
def canner_scope
case @method
when :index
User.where(company_id: @current_branch.company.id)
else
User.none
end
end
def can?
case @method
when :new, :index, :create, :update, :edit
has_role?(:admin)
else
false
end
end
end
can?
You probably recognize this method from Ryan Bates' cancan gem. The idea is the same as well.
You use the can method to determine if the current_user is able to access an action or resource.
The example above uses a straightforward case statement to determine if the current_user can access the current action or resource.
The symbols in the when portion of the case match your typical actions in the example but they can be whatever you want really.
case @method
when :something_random
has_role?(:admin)
else
false
end
# Then in controller do:
can?(:something_random, :customer)
In english the can method is saying:
Can the currently signed in user access the something_random action? Oh, and by the way please use the CustomerPolicy's can? method to do the checking.
can?(:something_random, :user) would use the ... you guessed it UserPolicy's can? method.
Force the Use of Policies
Also like Pundit you can force your app to use policies. I recommend you do this so you don't forget to wrap authorization about some of your resources.
To make sure your controller actions are using the can? method add this to your application_controller.rb
after_action :ensure_auth
And to make sure you are using the canner_scope do the following:
after_action :ensure_scope, only: :index
Note the use of only here. You usually won't need the canner_scope on anything except for the index to be strictly enforced.
Handle Canner Authorization Failures
When a user does stumble onto something they don't have access to you'll want to politely tell them about it and direct the app flow as you see fit.
To accomplish this in your application_controller.rb add
rescue_from Canner::NotAuthorizedError, with: :user_not_authorized
You can name your method whatever you want. Mine is user_not_authorized and looks like this:
private
def (exception)
flash[:error] = exception.
redirect_to(request.referrer || root_path)
end
Using can? in views
You'll likely want to show or hide on screen items based on a users' role. This is done in canner like this:
= link_to 'Create Customer', new_customer_path if canner_policy(:new, :customer).can?
This will look in the CustomerPolicy's can? method implemention and follow whatever rules you have for the :new symbol.
So assuming the CustomerPolicy can? method provided below the currently signed in user would only be able to see the create customer link if they had an admin role.
def can?
case @method
when :new
has_role?(:admin)
else
false
end
end