1

I am implementing pundit and wish to restrict the user#edit and user#update actions to only the current_user

def edit
  @user = current_user
  authorize(@user)
end

def update
  @user = current_user
  authorise(@user)
  if @user.update_attributes(user_params)
    flash[:success] = "Profile updated"
    redirect_to edit_user_path
  else
    render 'edit'
  end
end

The following is my attempted policy which (a) does not work and (b) is illogical.

class UserPolicy

  attr_reader :user, :user

  def initialise(user, user)
    @user = user
  end

  def update?
    true
  end

  alias_method :edit?, :update?

end

I have now updated my UserPolicy as per below. I have set the actions to false for testing as everything was being authorised:

class UserPolicy < ApplicationPolicy

  def new?
    create?
  end

  def create?
    false
  end

  def edit?
    update?
  end

  def update?
    false
    #user.id == record.id
  end

end

However my policies are not recognised. Upon further reading I added the following to my ApplicationController:

after_filter :verify_authorized, except: :index
after_filter :verify_policy_scoped, only: :index

When I now navigate to my user#edit action I receive:

Pundit::AuthorizationNotPerformedError
mikefrey
  • 4,653
  • 3
  • 29
  • 40
Dercni
  • 1,216
  • 3
  • 18
  • 38

1 Answers1

2

First, make sure you have...

your-app/app/controllers/application_controller.rb

class ApplicationController < ActionController::Base
  include Pundit
end

your-app/app/policies/application_policy.rb with default permissions for common actions.

class ApplicationPolicy
  attr_reader :user, :record

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

  def index?
    false
  end

  def show?
    scope.where(:id => record.id).exists?
  end

  def create?
    false
  end

  def new?
    create?
  end

  def update?
    false
  end

  def edit?
    update?
  end

  def destroy?
    false
  end

  def scope
    Pundit.policy_scope!(user, record.class)
  end

  class Scope
    attr_reader :user, :scope

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

    def resolve
      scope
    end
  end

Then, in your UserPolicy

your-app/app/policies/section_policy.rb

class UserPolicy < ApplicationPolicy
  def edit?
    user.id == record.id
  end

  def update?
    edit?
  end
end

So, by default, user will be your current user and record will be the @user defined on edit and update actions.

You don't need to call authorize method explicitly. Pundit knows what to do with your @user attribute. So, your controller should be:

def edit
  user
end

def update
  if user.update_attributes(user_params)
    flash[:success] = "Profile updated"
    redirect_to edit_user_path
  else
    render 'edit'
  end
end

private

def user
  @user ||= User.find(params[:id])
end

you must know if you don't have a current_user method, yo will need to define a pundit_user in your application controller.

Leantraxxx
  • 4,506
  • 3
  • 38
  • 56
  • Thank you very much. So the ApplicationPolicy specified default permissions for the REST actions that can be overridden. i.e. by default update? is false and edit? uses the update? defaults. However these defaults are overridden by the UserPolicy that permits if the current_user is the record owner? – Dercni Aug 20 '15 at 23:56
  • Yes, your are overriding the defaults `update?` and `edit?` in the `UserPolicy` (`UserPolicy` Inherits from `ApplicationPolicy`) It's no mandatory to use the application policy but, It's a good practice. In your policies you override what you need only. – Leantraxxx Aug 21 '15 at 00:12
  • I am unclear of how record gets set in the ApplicationPolicy. For example if I was to create a UserPolicy for "new" there would be no user instance at that point so what would record be set to? – Dercni Aug 21 '15 at 00:32
  • You always have an `@user`. In your new action you can have `@user = User.new` – Leantraxxx Aug 21 '15 at 00:46
  • If, for some reason (not the new action case), you don't have an `@user`, you can always call the `authorize the_object_you_want, :the_policy_action_you_want?` https://github.com/elabs/pundit#headless-policies – Leantraxxx Aug 21 '15 at 00:55
  • Ok, and this would explain why index is the exception to the rule as it is a class action whereas the others are instance actions. – Dercni Aug 21 '15 at 00:55
  • `the_object_you_want` will be `record` inside de policy – Leantraxxx Aug 21 '15 at 00:56
  • I'm guessing that if I don't specify an object Pundit knows that within the user controller the record should be (at)user and within an article controller the record should be (at)article etc – Dercni Aug 21 '15 at 00:58
  • yes, thats why I said: *You don't need to call authorize method explicitly. Pundit knows what to do with your @user attribute* – Leantraxxx Aug 21 '15 at 01:01
  • Thanks for all the help. awesome :) Got another similar qn but I'll start a new thread. – Dercni Aug 21 '15 at 01:02
  • You welcome. You need to read about scopes to work with collection actions like index. https://github.com/elabs/pundit#scopes – Leantraxxx Aug 21 '15 at 01:04
  • To get it to work I need "authorize @user" in my users#edit action? – Dercni Aug 21 '15 at 11:17