From 1af6c6f395602019b079e457e00f33815398fefc Mon Sep 17 00:00:00 2001 From: Ryan Bates Date: Fri, 3 Sep 2010 14:38:55 -0700 Subject: [PATCH] adding check_authorization and skip_authorization controller class methods to ensure authorization is triggered (thanks justinko) - closes #135 --- lib/cancan/controller_additions.rb | 15 +++++++ lib/cancan/exceptions.rb | 3 ++ spec/cancan/ability_spec.rb | 28 +++++++++++++ spec/cancan/controller_additions_spec.rb | 52 ++++++++++++------------ 4 files changed, 71 insertions(+), 27 deletions(-) diff --git a/lib/cancan/controller_additions.rb b/lib/cancan/controller_additions.rb index b4e9054..f86e77a 100644 --- a/lib/cancan/controller_additions.rb +++ b/lib/cancan/controller_additions.rb @@ -151,6 +151,20 @@ module CanCan def authorize_resource(*args) ControllerResource.add_before_filter(self, :authorize_resource, *args) end + + def skip_authorization(*args) + self.before_filter(*args) do |controller| + controller.instance_variable_set(:@_authorized, true) + end + end + + def check_authorization(*args) + self.after_filter(*args) do |controller| + unless controller.instance_variable_defined?(:@_authorized) + raise AuthorizationNotPerformed, "This action does not authorize the user. Add authorize! or authorize_resource to the controller." + end + end + end end def self.included(base) @@ -186,6 +200,7 @@ module CanCan # See the load_and_authorize_resource method to automatically add the authorize! behavior # to the default RESTful actions. def authorize!(*args) + @_authorized = true current_ability.authorize!(*args) end diff --git a/lib/cancan/exceptions.rb b/lib/cancan/exceptions.rb index 177584f..c9deb1f 100644 --- a/lib/cancan/exceptions.rb +++ b/lib/cancan/exceptions.rb @@ -5,6 +5,9 @@ module CanCan # Raised when removed code is called, an alternative solution is provided in message. class ImplementationRemoved < Error; end + # Raised when using check_authorization without calling authorized! + class AuthorizationNotPerformed < Error; end + # This error is raised when a user isn't allowed to access a given controller action. # This usually happens within a call to ControllerAdditions#authorize! but can be # raised manually. diff --git a/spec/cancan/ability_spec.rb b/spec/cancan/ability_spec.rb index 94037f5..a07b56e 100644 --- a/spec/cancan/ability_spec.rb +++ b/spec/cancan/ability_spec.rb @@ -265,6 +265,34 @@ describe CanCan::Ability do @ability.attributes_for(:new, Range).should == {:foo => "foo", :bar => 123, :baz => "baz"} end + it "should raise access denied exception if ability us unauthorized to perform a certain action" do + begin + @ability.authorize! :read, :foo, 1, 2, 3, :message => "Access denied!" + rescue CanCan::AccessDenied => e + e.message.should == "Access denied!" + e.action.should == :read + e.subject.should == :foo + else + fail "Expected CanCan::AccessDenied exception to be raised" + end + end + + it "should not raise access denied exception if ability is authorized to perform an action" do + @ability.can :read, :foo + lambda { @ability.authorize!(:read, :foo) }.should_not raise_error + end + + it "should raise access denied exception with default message if not specified" do + begin + @ability.authorize! :read, :foo + rescue CanCan::AccessDenied => e + e.default_message = "Access denied!" + e.message.should == "Access denied!" + else + fail "Expected CanCan::AccessDenied exception to be raised" + end + end + describe "unauthorized message" do after(:each) do I18n.backend = nil diff --git a/spec/cancan/controller_additions_spec.rb b/spec/cancan/controller_additions_spec.rb index 4385e12..8d514aa 100644 --- a/spec/cancan/controller_additions_spec.rb +++ b/spec/cancan/controller_additions_spec.rb @@ -14,33 +14,10 @@ describe CanCan::ControllerAdditions do lambda { @controller.unauthorized! }.should raise_error(CanCan::ImplementationRemoved) end - it "should raise access denied exception if ability us unauthorized to perform a certain action" do - # TODO this should probably be moved into Ability spec - begin - @controller.authorize! :read, :foo, 1, 2, 3, :message => "Access denied!" - rescue CanCan::AccessDenied => e - e.message.should == "Access denied!" - e.action.should == :read - e.subject.should == :foo - else - fail "Expected CanCan::AccessDenied exception to be raised" - end - end - - it "should not raise access denied exception if ability is authorized to perform an action" do - @controller.current_ability.can :read, :foo - lambda { @controller.authorize!(:read, :foo) }.should_not raise_error - end - - it "should raise access denied exception with default message if not specified" do - begin - @controller.authorize! :read, :foo - rescue CanCan::AccessDenied => e - e.default_message = "Access denied!" - e.message.should == "Access denied!" - else - fail "Expected CanCan::AccessDenied exception to be raised" - end + it "authorize! should assign @_authorized instance variable and pass args to current ability" do + mock(@controller.current_ability).authorize!(:foo, :bar) + @controller.authorize!(:foo, :bar) + @controller.instance_variable_get(:@_authorized).should be_true end it "should have a current_ability method which generates an ability for the current user" do @@ -76,4 +53,25 @@ describe CanCan::ControllerAdditions do mock(@controller_class).before_filter(:only => [:show, :index]) { |options, block| block.call(@controller) } @controller_class.load_resource :foo => :bar, :only => [:show, :index] end + + it "skip_authorization should set up a before filter which sets @_authorized to true" do + mock(@controller_class).before_filter(:filter_options) { |options, block| block.call(@controller) } + @controller_class.skip_authorization(:filter_options) + @controller.instance_variable_get(:@_authorized).should be_true + end + + it "check_authorization should trigger AuthorizationNotPerformed in after filter" do + mock(@controller_class).after_filter(:some_options) { |options, block| block.call(@controller) } + lambda { + @controller_class.check_authorization(:some_options) + }.should raise_error(CanCan::AuthorizationNotPerformed) + end + + it "check_authorization should not raise error when @_authorized is set" do + @controller.instance_variable_set(:@_authorized, true) + mock(@controller_class).after_filter(:some_options) { |options, block| block.call(@controller) } + lambda { + @controller_class.check_authorization(:some_options) + }.should_not raise_error(CanCan::AuthorizationNotPerformed) + end end