Search code examples
ruby-on-railsruby-on-rails-4authorizationpundit

Rails 4: "Pundit::NotAuthorizedError"


In my Rails 4 app, there are 5 models:

class User < ActiveRecord::Base
  has_many :administrations
  has_many :calendars, through: :administrations
end

class Calendar < ActiveRecord::Base
  has_many :administrations
  has_many :users, through: :administrations
  has_many :posts
end

class Administration < ActiveRecord::Base
  belongs_to :user
  belongs_to :calendar
end

class Post < ActiveRecord::Base
  belongs_to :calendar
end

class Comment < ActiveRecord::Base
  belongs_to :post
  belongs_to :user
end

I implemented authentication with Devise (so we have access to current_user).

Now, I am trying to implement authorization with Pundit (first timer).

Following the documentation, I installed the gem and ran the rails g pundit:install generator.

Then, I created a CalendarPolicy, as follows:

class CalendarPolicy < ApplicationPolicy

  attr_reader :user, :calendar

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

  def index?
    user.owner? || user.editor? || user.viewer?
  end

  def show?
    user.owner? || user.editor? || user.viewer?
  end

  def update?
    user.owner? || user.editor?
  end

  def edit?
    user.owner? || user.editor?
  end

  def destroy?
    user.owner?
  end

end

I also updated my User model with the following methods:

def owner?
  Administration.find_by(user_id: params[:user_id], calendar_id: params[:calendar_id]).role == "Owner"
end

def editor?
  Administration.find_by(user_id: params[:user_id], calendar_id: params[:calendar_id]).role == "Editor"
end

def viewer?
  Administration.find_by(user_id: params[:user_id], calendar_id: params[:calendar_id]).role == "Viewer"
end

I updated my CalendarsController actions with authorize @calendar, as follows:

  def index
    @user = current_user
    @calendars = @user.calendars.all
  end

  # GET /calendars/1
  # GET /calendars/1.json
  def show
    @user = current_user
    @calendar = @user.calendars.find(params[:id])
    authorize @calendar
  end

  # GET /calendars/new
  def new
    @user = current_user
    @calendar = @user.calendars.new
    authorize @calendar
  end

  # GET /calendars/1/edit
  def edit
    @user = current_user
    authorize @calendar
  end

  # POST /calendars
  # POST /calendars.json
def create
  @user = current_user
  @calendar = @user.calendars.create(calendar_params)
  authorize @calendar
  respond_to do |format|
    if @calendar.save
      current_user.set_default_role(@calendar.id, 'Owner')
      format.html { redirect_to calendar_path(@calendar), notice: 'Calendar was successfully created.' }
      format.json { render :show, status: :created, location: @calendar }
    else
      format.html { render :new }
      format.json { render json: @calendar.errors, status: :unprocessable_entity }
    end
  end
end

  # PATCH/PUT /calendars/1
  # PATCH/PUT /calendars/1.json
  def update
    @user = current_user
    @calendar = Calendar.find(params[:id])
    authorize @calendar
    respond_to do |format|
      if @calendar.update(calendar_params)
        format.html { redirect_to calendar_path(@calendar), notice: 'Calendar was successfully updated.' }
        format.json { render :show, status: :ok, location: @calendar }
      else
        format.html { render :edit }
        format.json { render json: @calendar.errors, status: :unprocessable_entity }
      end
    end
  end

  # DELETE /calendars/1
  # DELETE /calendars/1.json
  def destroy
    @user = current_user
    @calendar.destroy
    authorize @calendar
    respond_to do |format|
      format.html { redirect_to calendars_url, notice: 'Calendar was successfully destroyed.' }
      format.json { head :no_content }
    end
  end

And I included after_action :verify_authorized, :except => :index in my ApplicationController.

Now, when I log in, I can access http://localhost:3000/calendars/ but when I try to visit http://localhost:3000/calendars/new, I get the following error:

Pundit::NotAuthorizedError in CalendarsController#new
not allowed to new? this #<Calendar id: nil, name: nil, created_at: nil, updated_at: nil>

  @user = current_user
  @calendar = @user.calendars.new
  authorize @calendar
end

Obviously, I must have done something wrong.

Problem: I can't figure out what.

Any idea?


Solution

  • The problem was that I had not defined a create action in the CalendarPolicy.

    Since the CalendarPolicy inherits from the ApplicationPolicy — CalendarPolicy < ApplicationPolicy — and the create action in the ApplicationPolicy is set to false by default, I was getting an error.

    Simply adding the following code to CalendarPolicy fixed the problem:

    def create?
      true
    end
    

    Bonus tip: there is no need to add a new action to CalendarPolicy since we already have the following code in ApplicationPolicy:

    def new?
      create?
    end