Browse files

Can now specify custom security violation handler

  • Loading branch information...
1 parent 6ece493 commit 69ad496de55c05aba9d15fbfdc36b6c7b6b4d506 @nathanl committed Apr 7, 2012
26 README.markdown
@@ -1,6 +1,6 @@
# Authority
-Authority gives you a clean and easy way to say, in your Rails app, **who** is allowed to do **what** with your models. Unauthorized actions get a warning and an entry in a log file.
+Authority gives you a clean and easy way to say, in your Rails app, **who** is allowed to do **what** with your models, and to handle and/or log unauthorized actions.
It requires that you already have some kind of user object in your application, accessible from all controllers and views via a method like `current_user` (configurable).
@@ -302,16 +302,30 @@ 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'"
+# 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
-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'
4 lib/authority/configuration.rb
@@ -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 = do |able, authorizer, user|
@@ -30,6 +30,8 @@ def initialize
@user_method = :current_user
+ @security_violation_handler = :authority_forbidden
@logger =
11 lib/authority/controller.rb
@@ -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
+ def self.security_violation_callback
+ 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
10 lib/generators/templates/authority_initializer.rb
@@ -70,10 +70,18 @@
# :update => 'updatable',
# :delete => 'deletable'
# }
+ # 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
# 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:
31 spec/authority/controller_spec.rb
@@ -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!
+ @fakeException =
+ @sample_controller =
+ # 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(@fakeException)
+ 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( {|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)
describe "after including" do

0 comments on commit 69ad496

Please sign in to comment.