Add ability to define custom exception handling. #3

Merged
merged 4 commits into from Apr 17, 2012
View
@@ -302,16 +302,31 @@ If the user isn't allowed to edit widgets, they won't see the link. If they're n
<a name="security_violations_and_logging">
## Security Violations & Logging
-Anytime a user attempts an unauthorized action, Authority does two things:
+Anytime a user attempts an unauthorized action, Authority calls whatever controller method is specified by your `security_violation_handler` option, handing it the exception. The default handler is `authority_forbidden`, which Authority adds to your `ApplicationController`. It does the following:
-- Renders your `public/403.html`
+- Renders `public/403.html`
- Logs the violation to whatever logger you configured.
-If you want to have nice log messages for security violations, you should ensure that your user object and models have `to_s` methods; this will control how they show up in log messages saying things like
+You can specify a different handler like so:
- "Kenneth Lay is not allowed to delete this resource: 'accounting_tricks.doc'"
+```ruby
+# config/initializers/authority.rb
+...
+config.security_violation_handler = :fire_ze_missiles
+...
+
+# app/controllers/application_controller.rb
+class ApplicationController < ActionController::Base
+
+ def fire_ze_missiles(exception)
+ # Log? Set a flash message? Dispatch minions to
+ # fill their mailbox with goose droppings? It's up to you.
+ end
+...
+end
+```
-If you feel like setting up a `cron` job to watch the log file, look up the user's name and address, and dispatch minions to fill their mailbox with goose droppings, that's really up to you. I got nothing to do with it, man.
+If you want different error handling per controller, define `fire_ze_missiles` on each of them.
<a name="credits">
## Credits, AKA 'Shout-Outs'
@@ -3,7 +3,7 @@ class Configuration
# Has default settings, overrideable in the initializer.
- attr_accessor :default_strategy, :abilities, :controller_action_map, :user_method, :logger
+ attr_accessor :default_strategy, :abilities, :controller_action_map, :user_method, :security_violation_handler, :logger
def initialize
@default_strategy = Proc.new do |able, authorizer, user|
@@ -30,6 +30,8 @@ def initialize
@user_method = :current_user
+ @security_violation_handler = :authority_forbidden
+
@logger = Logger.new(STDERR)
end
@@ -6,10 +6,18 @@ module Controller
extend ActiveSupport::Concern
included do
- rescue_from Authority::SecurityViolation, :with => :authority_forbidden
+ rescue_from(Authority::SecurityViolation, :with => Authority::Controller.security_violation_callback)
class_attribute :authority_resource
end
+ def self.security_violation_callback
+ Proc.new do |exception|
+ # Through the magic of ActiveSupport's Proc#bind, `ActionController::Base#rescue_from`
+ # can call this proc and make `self` the actual controller instance
+ self.send(Authority.configuration.security_violation_handler, exception)
+ end
+ end
+
module ClassMethods
# Sets up before_filter to ensure user is allowed to perform a given controller action
@@ -37,6 +45,7 @@ def authority_action(action_map)
def authority_action_map
@authority_action_map ||= Authority.configuration.controller_action_map.dup
end
+
end
protected
@@ -70,10 +70,18 @@
# :update => 'updatable',
# :delete => 'deletable'
# }
+
+ # SECURITY_VIOLATION_HANDLER
+ # If a SecurityViolation is raised, what controller method should be used to rescue it?
+ #
+ # Default is:
+ #
+ # config.security_violation_handler = :authority_forbidden # Defined in controller.rb
# LOGGER
# If a user tries to perform an unauthorized action, where should we log that fact?
- # Provide a logger object which responds to `.warn(message)`
+ # Provide a logger object which responds to `.warn(message)`, unless your
+ # security_violation_handler calls a different method.
#
# Default is:
#
@@ -3,14 +3,41 @@
require 'support/example_controllers'
require 'support/mock_rails'
require 'support/user'
+require 'active_support/core_ext/proc'
describe Authority::Controller do
+ describe "the security violation callback" do
+
+ it "should call whatever method on the controller that the configuration specifies" do
+ # Here be dragons!
+ @fake_exception = Exception.new
+ @sample_controller = SampleController.new
+ # If a callback is passed to a controller's `rescue_from` method as the value for
+ # the `with` option (like `SomeController.rescue_from FooException, :with => some_callback`),
+ # Rails will use ActiveSupport's `Proc#bind` to ensure that when the proc refers to
+ # `self`, it will be the controller, not the proc itself.
+ # I need this callback's `self` to be the controller for the purposes of
+ # this test, so I'm stealing that behavior.
+ @callback = Authority::Controller.security_violation_callback.bind(@sample_controller)
+
+ Authority.configuration.security_violation_handler = :fire_ze_missiles
+ @sample_controller.should_receive(:fire_ze_missiles).with(@fake_exception)
+ @callback.call(@fake_exception)
+ end
+ end
+
describe "when including" do
- it "should specify rescuing security transgressions" do
- SampleController.should_receive(:rescue_from).with(Authority::SecurityViolation, :with => :authority_forbidden)
+
+ before :each do
+ Authority::Controller.stub(:security_violation_callback).and_return(Proc.new {|exception| })
+ end
+
+ it "should specify rescuing security violations with a standard callback" do
+ SampleController.should_receive(:rescue_from).with(Authority::SecurityViolation, :with => Authority::Controller.security_violation_callback)
SampleController.send(:include, Authority::Controller)
end
+
end
describe "after including" do