Pundit
Authorization done right
What is authorization?
Authorization is determining if given user has access to given resource.
Where does authorization fit in (Rails-flavoured) MVC?
- Model - should not need to know anything about the user.
- View - knows about user and the resource but should not decide about anything.
- Controller - the right place for authorization
Pundit
- Minimal authorization through OO design and pure Ruby classes
- https://github.com/elabs/pundit
- Second (after CanCan) in authorization on Ruby Toolbox
- Better than CanCan, which stores all authorization rules in one huge file
The term originates from the Sanskrit term pandit (paṇḍitá), meaning "learned". It refers to someone who is erudite in various subjects and who conducts religious ceremonies and offers counsel to the king.
Nowadays, a pundit (sometimes called a talking head) is a person who offers to mass media their opinion or commentary on a particular subject area on which they are knowledgeable (or can at least appear to be knowledgeable), or considered a scholar in said area.
CanCan way
class Ability
include CanCan::Ability
def initialize(user)
user ||= User.new
unless user.new_record?
can :comment, Part
can :rate, Part
cannot :rate, Part, :story => { :user => user }
can :comment, Article
can :comment, NewsPost
can :create, Story
can :update, User, :id => user.id
can :update, Story, :user => user
can :update, Part, :story => { :user => user, :status => Story::OPEN }
can :update, Part, :story => { :user => user, :story_type => Story::SINGLE }
cannot :add_fragment_to, Story, :story_type => Story::SINGLE
can :add_fragment_to, Story, :user => user, :status => Story::OPEN
end
if user.has_role?(:news_editor)
can :manage, NewsPost
end
if user.has_role?(:stories_editor)
can :manage, Story
can :manage, Part
end
if user.has_role?(:users_manager)
can :manage, User
end
if user.has_role?(:comments_editor)
can :manage, Comment
end
if user.has_role?(:articles_editor)
can :manage, Article
end
end
end
Pundit way
app/policies/document_policy.rb
class DocumentPolicy
attr_reader :user, :document
def initialize(user, document)
@user = user
@document = document
end
def view?
@document.user == user
end
end
user = current_user
document (resource) = any kind of ruby object
Pundit way - controller
def show
@document = Document.find(params[:id])
@document_file = @document.last_file
authorize @document, :view?
if File.exists?(@document_file.file.path)
send_file @document_file.file.path, type: 'application/pdf',
disposition: 'inline'
else
render :file_not_found
end
end
Infer method name from controller action
# policy
class PostPolicy < ApplicationPolicy
def update?
user.admin? or not record.published?
end
end
# controller
def update
@post = Post.find(params[:id])
authorize @post
if @post.update(post_params)
redirect_to @post
else
render :edit
end
end
View
Spoiler: this is where CanCan wins
# Pundit
<% if policy(@post).update? %>
<%= link_to "Edit post", edit_post_path(@post) %>
<% end %>
# CanCan
<% if can? :update, @article %>
<%= link_to "Edit", edit_article_path(@article) %>
<% end %>
Handle unauthorized acccess
class ApplicationController < ActionController::Base
include Pundit
rescue_from Pundit::NotAuthorizedError, with: :permission_denied
def permission_denied
render 'shared/permission_denied'
end
end
Thank you
This was (hopefully) a part of Gems 101 series, where we discuss how it's done without Framework and why it's better/worse way.
pundit
By katafrakt
pundit
- 1,016