Authorization with Pundit
What isĀ Pundit?
Authorization strategies
User-based
class Ability
include CanCan::Ability
def initialize(user)
# Anonymous users don't have access to anything
return if user.nil?
case user.role
when :admin
can :manage, :all
when :supervisor
# Between 1-50 can/cannot statements
when :doctor
# Between 1-50 can/cannot statements
when :patient
# Between 1-50 can/cannot statements
else
raise Ability::UnknownRoleError
end
end
end
Role-based
class PostPolicy < ApplicationPolicy
def show?
true
end
def create?
user.admin?
end
def update?
user.admin? or not post.published?
end
def destroy?
update?
end
end
First policy
# app/policies/post_policy.rb
class PostPolicy
attr_reader :user, :post
def initialize(user, post)
@user = user
@post = post
end
def create?
true
end
def update?
user.admin? || user.owner_of?(post)
end
end
Using in controller
# app/controllers/posts_controller.rb
class PostsController < ApplicationController
def create
raise unless PostPolicy.new(current_user, nil).create?
# [...]
end
def update
@post = Post.find(params[:id])
raise unless PostPolicy.new(current_user, @post).update?
# [...]
end
end
Using in controller
# app/controllers/posts_controller.rb
class PostsController < ApplicationController
include Pundit
def create
authorize Post, :create?
# [...]
end
def update
@post = Post.find(params[:id])
authorize @post, :update?
# [...]
end
end
Using in controller
# app/controllers/posts_controller.rb
class PostsController < ApplicationController
include Pundit
def create
authorize Post
# [...]
end
def update
@post = Post.find(params[:id])
authorize @post
# [...]
end
end
Using in view
# app/views/posts/show.html.erb
<% if policy(@post).update? %>
<%= link_to 'Edit post', edit_post_path(@post) %>
<% end %>
Testing policy
# spec/policies/post_policy_spec.rb
describe PostPolicy do
subject { described_class }
let(:post) { Post.new }
context "#update?" do
it "should disallow for other user" do
user = User.create
policy = subject.new(user, post)
expect(policy.update?).to be_false
end
it "should allow for admin" do
user = User.create(admin: true)
policy = subject.new(user, post)
expect(policy.update?).to be_true
end
end
end
Testing policy
# spec/policies/post_policy_spec.rb
describe PostPolicy do
subject { described_class }
let(:post) { Post.new }
permissions :update? do
it "should disallow for other user" do
user = User.new
expect(subject).not_to permit(user, post)
end
it "should allow for admin" do
user = User.new(admin: true)
expect(subject).to permit(user, post)
end
end
end
Scopes
# app/policies/post_policy.rb
class PostPolicy
# [...]
class Scope < Struct.new(:user, :scope)
def resolve
if user.admin?
scope.all
else
scope.where(user_id: user.id)
end
end
end
end
# app/controllers/posts_controller.rb
class PostsController < ApplicationController
def index
@posts = PostPolicy::Scope.new(current_user, Post).resolve
end
end
Scopes
# app/policies/post_policy.rb
class PostPolicy
# [...]
class Scope < Struct.new(:user, :scope)
def resolve
if user.admin?
scope.all
else
scope.where(user_id: user.id)
end
end
end
end
# app/controllers/posts_controller.rb
class PostsController < ApplicationController
def index
@posts = policy_scope(Post)
end
end
Scopes
# app/controllers/posts_controller.rb
class PostsController < ApplicationController
def index
@posts = policy_scope(Post)
end
def show
@post = policy_scope(Post).find(params[:id])
end
end
# app/views/posts/index.html.erb
<% policy_scope(@user.posts).each do |post| %>
<p><%= link_to post.title, post_path(post) %></p>
<% end %>
Strong params
# app/policies/post_policy.rb
class PostPolicy
def permitted_attributes
if user.admin? || user.owner_of?(post)
[:title, :body, :tag_list]
else
[:tag_list]
end
end
end
# app/controllers/posts_controller.rb
class PostsController < ApplicationController
def post_params
params.require(:post).permit(policy(@post).permitted_attributes)
end
end
Strong params
# app/policies/post_policy.rb
class PostPolicy
def permitted_attributes
if user.admin? || user.owner_of?(post)
[:title, :body, :tag_list]
else
[:tag_list]
end
end
end
# app/controllers/posts_controller.rb
class PostsController < ApplicationController
def post_params
permitted_attributes(@post)
end
end
Ensuring policies are usedĀ
# app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
after_action :verify_authorized
after_action :verify_policy_scoped, :only => :index
end
# app/controllers/posts_controller.rb
class PostsController < ApplicationController
def show
post = Post.find_by(attribute: "value")
if post.present?
authorize post
else
skip_authorization
end
end
end
Remember:
It's all PORO!
Thanks!
Authorization with Pundit
By Bernard Potocki
Authorization with Pundit
- 1,183