CanCan load_and_authorize_resource triggers Forbidden Attributes
Asked Answered
T

2

17

I have a standard RESTful controller that uses strong parameters.

class UsersController < ApplicationController
  respond_to :html, :js

  def index
    @users = User.all
  end

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

  def new
    @user = User.new
  end

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

  def create
    @user = User.new(safe_params)

    if @user.save
      redirect_to @user, notice: t('users.controller.create.success')
    else
      render :new
    end
  end

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

    if @user.update_attributes(safe_params)
      redirect_to @user, notice: t('users.controller.update.success')
    else
      render :edit
    end
  end

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

    if current_user != @user
      @user.destroy
    else
      flash[:error] = t('users.controller.destroy.prevent_self_destroy')
    end
    redirect_to users_url
  end

  private

  def safe_params
    safe_attributes =
      [
        :first_name,
        :last_name,
        :email,
        :password,
        :password_confirmation,
      ]
    if current_user.is?(:admin)
      safe_attributes += [:role_ids]
    end
    params.require(:user).permit(*safe_attributes)
  end
end

In my config/initializers I have the file strong_parameters.rb

ActiveRecord::Base.send(:include,  ActiveModel::ForbiddenAttributesProtection)

When I add a simple call to CanCan's load_and_authorize_resource I get

1) UsersController POST create with invalid params re-renders the 'new' template
 Failure/Error: post :create, user: @attr
 ActiveModel::ForbiddenAttributes:
   ActiveModel::ForbiddenAttributes
 # ./spec/controllers/users_controller_spec.rb:128:in `block (4 levels) in <top (required)>'

Where @attr in the test is defined as

  before(:each) do
    @attr =
      {
        first_name: "John",
        last_name: "Doe",
        email: "[email protected]",
        password: "foobar",
        password_confirmation: "foobar"
      }
  end

In the tests I have it all setup properly to login the user and give them the necessary roles for being an administrator so I know it's not that. I don't know why this is causing ForbiddenAttributes to trigger. I'm sure it's something simple I've overlooked. Has anyone else encountered this problem and found a solution to it?

Tenon answered 6/10, 2012 at 3:34 Comment(0)
G
20

I believe this is because CanCan will use its own getter method for the requested resource if you don't pre-load it with a before_filter. So you could add this to the controller and it should work:

class UsersController < ApplicationController
  before_filter :new_user, :only => [:new, :create]

  load_and_authorize_resource

  def new_user
    @user = User.new(safe_params)
  end
end

(And then do the same for the edit/update actions.)

Girder answered 6/10, 2012 at 3:39 Comment(8)
I believe I am having the same issue. Would you mind clarifying your solution further?Sorn
It's been a while but I'll give it a shot ;) What part is giving you trouble? Basically if you call cancan's load_and_authorize_resource it will try to load the most "logical" resource given the controller's name, which precedes the operation of the strong parameters gem. In this case it will try to build the User, @user = User.new(params[:user]) But strong_parameters won't allow mass assignment this way. If you use a before_filter to set the @user instance variable, CanCan will just use that instead. If your before_filter complies with strong_parameters it shouldn't raise an error.Girder
Ahhh Much appreciated! I wasn't understanding how CanCan was "...[using] its own getter method..." but that makes a lot of sense now. Thanks again!Sorn
Glad I could help! Good luck with your project!Girder
@PeterDuijnstee: I believe you want to have :only => [:create], instead of :only => [:new, :create]. :new won't have any parameters at all. So, it will choke in safe_params on params.require(:user).permit(*safe_attributes)Hedrick
Oh of course, it requires params[:user]. Don't have a whole lot of experience with Strong Parameters yet so I missed that one, good catch :)Girder
@PeterDuijnstee , thank you for your answer. But thank you more for your warning at your comment! Instance name makes difference in this issue! My controller was Admin::Ads but my model was Ad , whenever my instance variable was @admin_ad it didn't work. But when I changed it to @ad it worked!Influence
Should check out the cancancommunity/cancancan gem. It is the new official home of CanCan and includes support for strong_parameters out of the box.Chester
W
7
before_filter do
  params[:user] = safe_params
end
load_and_authorize_resource
Wolgast answered 9/5, 2013 at 6:30 Comment(1)
Major emphasis here on the before_filter coming before load_and_authorize_resourceSupervene

© 2022 - 2024 — McMap. All rights reserved.