0

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?

Thibaud Clement
  • 6,607
  • 10
  • 50
  • 103

2 Answers2

1

You don't have access to the params in the model unless you pass them through. You should pass the calendar to the model instance function and you already have access to the user.

user.editor?(calendar)

def editor?(calendar)
  Administration.find_by(user_id: self.id, calendar_id: calendar.id).role == "Editor"
end
penner
  • 2,707
  • 1
  • 37
  • 48
  • Ok, so I updated all 3 methods in the `User` model as well as the `CalendarPolicy` as per your answer. But I still get the same error message. Any other idea? – Thibaud Clement Sep 09 '15 at 23:27
  • 1
    Are you using pry or debugger? I would put one in the controller, one in the authorize method and one in the model. Then make sure all the variables are set as expected along the way. – penner Sep 09 '15 at 23:30
  • I don't use Pry nor Debugger. I have Byebug installed in development and test. What do you mean by "I would put one in the controller, one in the authorize method and one in the model."? – Thibaud Clement Sep 09 '15 at 23:38
0

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
Thibaud Clement
  • 6,607
  • 10
  • 50
  • 103