Authorization with Pundit

Episode #47 by Teacher's Avatar David Kimura

Summary

Prevent unauthorized access to your application with Pundit; an authorization library for Ruby on Rails which restricts what resources a given user is allowed to access.
rails security authorization 6:31

Resources

Summary

# Gemfile
gem 'pundit'

# application_controller.rb
class ApplicationController < ActionController::Base
  # Prevent CSRF attacks by raising an exception.
  # For APIs, you may want to use :null_session instead.
  include Pundit
  protect_from_forgery with: :exception

  rescue_from Pundit::NotAuthorizedError do 
    redirect_to root_url, alert: 'You do not have access to this page'
  end
end

# articles/show.html.erb
<%= link_to 'Edit', edit_article_path(@article) if policy(@article).update? %>
<%= link_to 'Delete', article_path(@article), method: :delete if policy(@article).destroy? %>

# policies/article_policy.rb
class ArticlePolicy < ApplicationPolicy
  class Scope < Scope
    def resolve
      scope.where(published: true).or(scope.where(user_id: @user.try(:id)))
    end
  end

  def new? ; user_is_owner_of_record? ; end
  def create? ; user_is_owner_of_record? ; end

  def show?
    user_is_owner_of_record? || @record.published
  end

  def update? ; user_is_owner_of_record? ; end
  def destroy? ; user_is_owner_of_record? ; end

  private

  def user_is_owner_of_record?
    @user == @record.user
  end
end

# articles_controller.rb
class ArticlesController < ApplicationController
  before_action :authenticate_user!, except: :index
  before_action :set_article, only: [:show, :edit, :update, :destroy]

  after_action :verify_authorized, except: :index
  after_action :verify_policy_scoped, only: :index

  def index
    # @articles = Article.all
    # @articles = Article.where(published: true)
    @articles = policy_scope(Article).reverse
  end

  def show
  end

  def new
    @article = current_user.articles.new
    authorize @article
  end

  def edit
  end

  def create
    @article = current_user.articles.create(article_params)
    authorize @article
    if @article.save
      redirect_to @article, notice: 'Article was successfully created.'
    else
      render :new
    end
  end

  def update
    if @article.update(article_params)
      redirect_to @article, notice: 'Article was successfully updated.'
    else
      render :edit
    end
  end

  def destroy
    @article.destroy
    redirect_to articles_url, notice: 'Article was successfully destroyed.'
  end

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

    def article_params
      params.require(:article).permit(:title, :user_id, :content, :published)
    end
end