diff --git a/CHANGELOG.rdoc b/CHANGELOG.rdoc index 4b7ecdcf..8c355db6 100644 --- a/CHANGELOG.rdoc +++ b/CHANGELOG.rdoc @@ -1,3 +1,7 @@ +Master + +* Adds support for strong_parameters (bryanrite) + 1.6.10 (May 7, 2013) * fix matches_conditons_hash for string values on 1.8 (thanks rrosen) diff --git a/README.rdoc b/README.rdoc index 9b3baf8d..a65ae004 100644 --- a/README.rdoc +++ b/README.rdoc @@ -1,6 +1,6 @@ = CanCan -{Gem Version}[http://badge.fury.io/rb/cancan] -{}[http://travis-ci.org/ryanb/cancan] +{Gem Version}[http://badge.fury.io/rb/cancan] +{}[http://travis-ci.org/ryanb/cancan] {}[https://codeclimate.com/github/ryanb/cancan] Wiki[https://github.com/ryanb/cancan/wiki] | RDocs[http://rdoc.info/projects/ryanb/cancan] | Screencast[http://railscasts.com/episodes/192-authorization-with-cancan] @@ -34,7 +34,7 @@ User permissions are defined in an +Ability+ class. CanCan 1.5 includes a Rails rails g cancan:ability -In Rails 2.3, just add a new class in `app/models/ability.rb` with the following contents: +In Rails 2.3, just add a new class in app/models/ability.rb with the following contents: class Ability include CanCan::Ability @@ -76,6 +76,36 @@ Setting this for every action can be tedious, therefore the +load_and_authorize_ See {Authorizing Controller Actions}[https://github.com/ryanb/cancan/wiki/authorizing-controller-actions] for more information. +==== Strong Parameters + +When using strong_parameters or Rails 4+, you have to sanitize inputs before saving the record, in actions such as :create and :update. + +By default, CanCan will try to sanitize the input on :create and :update routes by seeing if your controller will respond to the following methods (in order): + +* create_params or update_params (depending on the action you are performing) +* _params such as article_params (this is the default convention in rails for naming your param method) +* resource_params (a generically named method you could specify in each controller) + +Additionally, load_and_authorize_resource can now take a param_method option to specify a custom method in the controller to run to sanitize input. + + class ArticlesController < ApplicationController + load_and_authorize_resource param_method: :my_sanitizer + + def create + if @article.save + # hurray + else + render :new + end + end + + private + + def my_sanitizer + params.require(:article).permit(:name) + end + end + === 3. Handle Unauthorized Access If the user authorization fails, a CanCan::AccessDenied exception will be raised. You can catch this and modify its behavior in the +ApplicationController+. diff --git a/lib/cancan/controller_resource.rb b/lib/cancan/controller_resource.rb index d7ca4bfb..d03ea748 100644 --- a/lib/cancan/controller_resource.rb +++ b/lib/cancan/controller_resource.rb @@ -220,7 +220,9 @@ def name end def resource_params - if @options[:class] + if param_actions.include?(@params[:action].to_sym) && params_method.present? + return @controller.send(params_method) + elsif @options[:class] params_key = extract_key(@options[:class]) return @params[params_key] if @params[params_key] end @@ -232,6 +234,19 @@ def resource_params_by_namespaced_name @params[extract_key(namespaced_name)] end + def params_method + params_methods.each do |method| + return method if @controller.respond_to?(method, true) + end + nil + end + + def params_methods + methods = ["#{@params[:action]}_params".to_sym, "#{name}_params".to_sym, :resource_params] + methods.unshift(@options[:param_method]) if @options[:param_method].present? + methods + end + def namespace @params[:controller].split(/::|\//)[0..-2] end @@ -258,6 +273,10 @@ def new_actions [:new, :create] + Array(@options[:new]) end + def param_actions + [:create, :update] + end + private def extract_key(value) diff --git a/spec/cancan/controller_resource_spec.rb b/spec/cancan/controller_resource_spec.rb index 2b88dddb..1e3223c9 100644 --- a/spec/cancan/controller_resource_spec.rb +++ b/spec/cancan/controller_resource_spec.rb @@ -492,4 +492,54 @@ class Section expect { resource.load_and_authorize_resource }.not_to raise_error expect(@controller.instance_variable_get(:@project)).to be_nil end + + context "with a strong parameters method" do + + it "only calls the santitize method with actions matching param_actions" do + @params.merge!(:controller => "project", :action => "update") + @controller.stub(:resource_params).and_return(resource: 'params') + resource = CanCan::ControllerResource.new(@controller) + resource.stub(param_actions: [:create]) + + @controller.should_not_receive(:send).with(:resource_params) + resource.send("resource_params") + end + + it "uses the specified option for santitizing input" do + @params.merge!(:controller => "project", :action => "create") + @controller.stub(:resource_params).and_return(resource: 'params') + @controller.stub(:project_params).and_return(model: 'params') + @controller.stub(:create_params).and_return(create: 'params') + @controller.stub(:custom_params).and_return(custom: 'params') + resource = CanCan::ControllerResource.new(@controller, {param_method: :custom_params}) + expect(resource.send("resource_params")).to eq(custom: 'params') + end + + it "prefers to use the create_params method for santitizing input" do + @params.merge!(:controller => "project", :action => "create") + @controller.stub(:resource_params).and_return(resource: 'params') + @controller.stub(:project_params).and_return(model: 'params') + @controller.stub(:create_params).and_return(create: 'params') + @controller.stub(:custom_params).and_return(custom: 'params') + resource = CanCan::ControllerResource.new(@controller) + expect(resource.send("resource_params")).to eq(create: 'params') + end + + it "prefers to use the _params method for santitizing input if create is not found" do + @params.merge!(:controller => "project", :action => "create") + @controller.stub(:resource_params).and_return(resource: 'params') + @controller.stub(:project_params).and_return(model: 'params') + @controller.stub(:custom_params).and_return(custom: 'params') + resource = CanCan::ControllerResource.new(@controller) + expect(resource.send("resource_params")).to eq(model: 'params') + end + + it "prefers to use the resource_params method for santitizing input if create or model is not found" do + @params.merge!(:controller => "project", :action => "create") + @controller.stub(:resource_params).and_return(resource: 'params') + @controller.stub(:custom_params).and_return(custom: 'params') + resource = CanCan::ControllerResource.new(@controller) + expect(resource.send("resource_params")).to eq(resource: 'params') + end + end end