From 57327119a865928ecd939c09de2f25af49651511 Mon Sep 17 00:00:00 2001 From: Ryan Bates Date: Sat, 8 Jan 2011 12:04:55 -0800 Subject: [PATCH] adding skip load and authorize behavior - closes #164 --- lib/cancan/controller_additions.rb | 50 +++++++++++++++++++++++ lib/cancan/controller_resource.rb | 27 +++++++++--- spec/cancan/controller_additions_spec.rb | 30 ++++++++++++++ spec/cancan/controller_resource_spec.rb | 52 +++++++++++++++++++++++- spec/cancan/inherited_resource_spec.rb | 4 +- 5 files changed, 156 insertions(+), 7 deletions(-) diff --git a/lib/cancan/controller_additions.rb b/lib/cancan/controller_additions.rb index 743a776..ba3e26b 100644 --- a/lib/cancan/controller_additions.rb +++ b/lib/cancan/controller_additions.rb @@ -166,6 +166,52 @@ module CanCan cancan_resource_class.add_before_filter(self, :authorize_resource, *args) end + # Skip both the loading and authorization behavior of CanCan for this given controller. This is primarily + # useful to skip the behavior of a superclass. You can pass :only and :except options to specify which actions + # to skip the effects on. It will apply to all actions by default. + # + # class ProjectsController < SomeOtherController + # skip_load_and_authorize_resource :only => :index + # end + # + # You can also pass the resource name as the first argument to skip that resource. + def skip_load_and_authorize_resource(*args) + skip_load_resource(*args) + skip_authorize_resource(*args) + end + + # Skip both the loading behavior of CanCan. This is useful when using +load_and_authorize_resource+ but want to + # only do authorization on certain actions. You can pass :only and :except options to specify which actions to + # skip the effects on. It will apply to all actions by default. + # + # class ProjectsController < ApplicationController + # load_and_authorize_resource + # skip_load_resource :only => :index + # end + # + # You can also pass the resource name as the first argument to skip that resource. + def skip_load_resource(*args) + options = args.extract_options! + name = args.first + cancan_skipper[:load][name] = options + end + + # Skip both the authorization behavior of CanCan. This is useful when using +load_and_authorize_resource+ but want to + # only do loading on certain actions. You can pass :only and :except options to specify which actions to + # skip the effects on. It will apply to all actions by default. + # + # class ProjectsController < ApplicationController + # load_and_authorize_resource + # skip_authorize_resource :only => :index + # end + # + # You can also pass the resource name as the first argument to skip that resource. + def skip_authorize_resource(*args) + options = args.extract_options! + name = args.first + cancan_skipper[:authorize][name] = options + end + # Add this to a controller to ensure it performs authorization through +authorized+! or +authorize_resource+ call. # If neither of these authorization methods are called, a CanCan::AuthorizationNotPerformed exception will be raised. # This is normally added to the ApplicationController to ensure all controller actions do authorization. @@ -209,6 +255,10 @@ module CanCan ControllerResource end end + + def cancan_skipper + @_cancan_skipper ||= {:authorize => {}, :load => {}} + end end def self.included(base) diff --git a/lib/cancan/controller_resource.rb b/lib/cancan/controller_resource.rb index c8467ae..6280e58 100644 --- a/lib/cancan/controller_resource.rb +++ b/lib/cancan/controller_resource.rb @@ -26,21 +26,38 @@ module CanCan end def load_resource - if load_instance? - self.resource_instance ||= load_resource_instance - elsif load_collection? - self.collection_instance ||= load_collection + unless skip?(:load) + if load_instance? + self.resource_instance ||= load_resource_instance + elsif load_collection? + self.collection_instance ||= load_collection + end end end def authorize_resource - @controller.authorize!(authorization_action, resource_instance || resource_class_with_parent) + unless skip?(:authorize) + @controller.authorize!(authorization_action, resource_instance || resource_class_with_parent) + end end def parent? @options.has_key?(:parent) ? @options[:parent] : @name && @name != name_from_controller.to_sym end + def skip?(behavior) # This could probably use some refactoring + options = @controller.class.cancan_skipper[behavior][@name] + if options.nil? + false + elsif options == {} + true + elsif options[:except] && ![options[:except]].flatten.include?(@params[:action].to_sym) + true + elsif [options[:only]].flatten.include?(@params[:action].to_sym) + true + end + end + protected def load_resource_instance diff --git a/spec/cancan/controller_additions_spec.rb b/spec/cancan/controller_additions_spec.rb index f8cd4a7..f831dca 100644 --- a/spec/cancan/controller_additions_spec.rb +++ b/spec/cancan/controller_additions_spec.rb @@ -83,4 +83,34 @@ describe CanCan::ControllerAdditions do stub(@controller.class).ancestors { ["InheritedResources::Actions"] } @controller.class.cancan_resource_class.should == CanCan::InheritedResource end + + it "cancan_skipper should be an empty hash with :authorize and :load options and remember changes" do + @controller_class.cancan_skipper.should == {:authorize => {}, :load => {}} + @controller_class.cancan_skipper[:load] = true + @controller_class.cancan_skipper[:load].should == true + end + + it "skip_authorize_resource should add itself to the cancan skipper with given model name and options" do + @controller_class.skip_authorize_resource(:project, :only => [:index, :show]) + @controller_class.cancan_skipper[:authorize][:project].should == {:only => [:index, :show]} + @controller_class.skip_authorize_resource(:only => [:index, :show]) + @controller_class.cancan_skipper[:authorize][nil].should == {:only => [:index, :show]} + @controller_class.skip_authorize_resource(:article) + @controller_class.cancan_skipper[:authorize][:article].should == {} + end + + it "skip_load_resource should add itself to the cancan skipper with given model name and options" do + @controller_class.skip_load_resource(:project, :only => [:index, :show]) + @controller_class.cancan_skipper[:load][:project].should == {:only => [:index, :show]} + @controller_class.skip_load_resource(:only => [:index, :show]) + @controller_class.cancan_skipper[:load][nil].should == {:only => [:index, :show]} + @controller_class.skip_load_resource(:article) + @controller_class.cancan_skipper[:load][:article].should == {} + end + + it "skip_load_and_authore_resource should add itself to the cancan skipper with given model name and options" do + @controller_class.skip_load_and_authorize_resource(:project, :only => [:index, :show]) + @controller_class.cancan_skipper[:load][:project].should == {:only => [:index, :show]} + @controller_class.cancan_skipper[:authorize][:project].should == {:only => [:index, :show]} + end end diff --git a/spec/cancan/controller_resource_spec.rb b/spec/cancan/controller_resource_spec.rb index 0989d44..668fcf6 100644 --- a/spec/cancan/controller_resource_spec.rb +++ b/spec/cancan/controller_resource_spec.rb @@ -3,10 +3,12 @@ require "spec_helper" describe CanCan::ControllerResource do before(:each) do @params = HashWithIndifferentAccess.new(:controller => "projects") - @controller = Object.new # simple stub for now + @controller_class = Class.new + @controller = @controller_class.new @ability = Ability.new(nil) stub(@controller).params { @params } stub(@controller).current_ability { @ability } + stub(@controller_class).cancan_skipper { {:authorize => {}, :load => {}} } end it "should load the resource into an instance variable if params[:id] is specified" do @@ -339,4 +341,52 @@ describe CanCan::ControllerResource do CanCan::ControllerResource.new(@controller, :nested => :project) }.should raise_error(CanCan::ImplementationRemoved) end + + it "should skip resource behavior for :only actions in array" do + stub(@controller_class).cancan_skipper { {:load => {nil => {:only => [:index, :show]}}} } + @params.merge!(:action => "index") + CanCan::ControllerResource.new(@controller).skip?(:load).should be_true + CanCan::ControllerResource.new(@controller, :some_resource).skip?(:load).should be_false + @params.merge!(:action => "show") + CanCan::ControllerResource.new(@controller).skip?(:load).should be_true + @params.merge!(:action => "other_action") + CanCan::ControllerResource.new(@controller).skip?(:load).should be_false + end + + it "should skip resource behavior for :only one action on resource" do + stub(@controller_class).cancan_skipper { {:authorize => {:project => {:only => :index}}} } + @params.merge!(:action => "index") + CanCan::ControllerResource.new(@controller).skip?(:authorize).should be_false + CanCan::ControllerResource.new(@controller, :project).skip?(:authorize).should be_true + @params.merge!(:action => "other_action") + CanCan::ControllerResource.new(@controller, :project).skip?(:authorize).should be_false + end + + it "should skip resource behavior :except actions in array" do + stub(@controller_class).cancan_skipper { {:load => {nil => {:except => [:index, :show]}}} } + @params.merge!(:action => "index") + CanCan::ControllerResource.new(@controller).skip?(:load).should be_false + @params.merge!(:action => "show") + CanCan::ControllerResource.new(@controller).skip?(:load).should be_false + @params.merge!(:action => "other_action") + CanCan::ControllerResource.new(@controller).skip?(:load).should be_true + CanCan::ControllerResource.new(@controller, :some_resource).skip?(:load).should be_false + end + + it "should skip resource behavior :except one action on resource" do + stub(@controller_class).cancan_skipper { {:authorize => {:project => {:except => :index}}} } + @params.merge!(:action => "index") + CanCan::ControllerResource.new(@controller, :project).skip?(:authorize).should be_false + @params.merge!(:action => "other_action") + CanCan::ControllerResource.new(@controller).skip?(:authorize).should be_false + CanCan::ControllerResource.new(@controller, :project).skip?(:authorize).should be_true + end + + it "should skip loading and authorization" do + stub(@controller_class).cancan_skipper { {:authorize => {nil => {}}, :load => {nil => {}}} } + @params.merge!(:action => "new") + resource = CanCan::ControllerResource.new(@controller) + lambda { resource.load_and_authorize_resource }.should_not raise_error + @controller.instance_variable_get(:@project).should be_nil + end end diff --git a/spec/cancan/inherited_resource_spec.rb b/spec/cancan/inherited_resource_spec.rb index d0bed43..77d3055 100644 --- a/spec/cancan/inherited_resource_spec.rb +++ b/spec/cancan/inherited_resource_spec.rb @@ -3,10 +3,12 @@ require "spec_helper" describe CanCan::InheritedResource do before(:each) do @params = HashWithIndifferentAccess.new(:controller => "projects") - @controller = Object.new # simple stub for now + @controller_class = Class.new + @controller = @controller_class.new @ability = Ability.new(nil) stub(@controller).params { @params } stub(@controller).current_ability { @ability } + stub(@controller_class).cancan_skipper { {:authorize => {}, :load => {}} } end it "show should load resource through @controller.resource" do