Skip to content
This repository
Browse code

removing unauthorized! in favor of authorize! and including more info…

…rmation in AccessDenied exception - closes #40
  • Loading branch information...
commit 8903feee70ed355af3752d85be7bd7e01d257d8c 1 parent ecf2818
Ryan Bates authored
4 CHANGELOG.rdoc
Source Rendered
... ... @@ -1,5 +1,9 @@
1 1 1.1.0 (not released)
2 2
  3 +* Removing "unauthorized!" method in favor of "authorize!"
  4 +
  5 +* Adding action, subject and default_message abilities to AccessDenied exception - see issue #40
  6 +
3 7 * Adding caching to current_ability controller method, if you're overriding this be sure to add caching too.
4 8
5 9 * Adding "accessible_by" method to Active Record for fetching records matching a specific ability
8 README.rdoc
Source Rendered
@@ -39,17 +39,17 @@ First, define a class called Ability in "models/ability.rb".
39 39
40 40 This is where all permissions will go. See the "Defining Abilities" section below for more information.
41 41
42   -You can access the current permissions at any point using the "can?" and "cannot?" methods in the view.
  42 +You can access the current permissions at any point using the "can?" and "cannot?" methods in the view and controller.
43 43
44 44 <% if can? :update, @article %>
45 45 <%= link_to "Edit", edit_article_path(@article) %>
46 46 <% end %>
47 47
48   -You can also use these methods in a controller along with the "unauthorized!" method to restrict access.
  48 +The "authorize!" method in the controller will raise CanCan::AccessDenied if the user is not able to perform the given action.
49 49
50 50 def show
51 51 @article = Article.find(params[:id])
52   - unauthorized! if cannot? :read, @article
  52 + authorize! :read, @article
53 53 end
54 54
55 55 Setting this for every action can be tedious, therefore the load_and_authorize_resource method is also provided to automatically authorize all actions in a RESTful style resource controller. It will set up a before filter which loads the resource into the instance variable and authorizes it.
@@ -71,6 +71,8 @@ If the user authorization fails, a CanCan::AccessDenied exception will be raised
71 71 end
72 72 end
73 73
  74 +See the CanCan::AccessDenied rdoc for more information on exception handling.
  75 +
74 76
75 77 == Defining Abilities
76 78
10 lib/cancan.rb
... ... @@ -1,14 +1,6 @@
1   -module CanCan
2   - # A general CanCan exception
3   - class Error < StandardError; end
4   -
5   - # This error is raised when a user isn't allowed to access a given
6   - # controller action. See ControllerAdditions#unauthorized! for details.
7   - class AccessDenied < Error; end
8   -end
9   -
10 1 require 'cancan/ability'
11 2 require 'cancan/controller_resource'
12 3 require 'cancan/resource_authorization'
13 4 require 'cancan/controller_additions'
14 5 require 'cancan/active_record_additions'
  6 +require 'cancan/exceptions'
35 lib/cancan/controller_additions.rb
@@ -85,7 +85,7 @@ def load_resource(options = {})
85 85 # and ensure the user can perform the current action on it. Under the hood it is doing
86 86 # something like the following.
87 87 #
88   - # unauthorized! if cannot?(params[:action].to_sym, @article || Article)
  88 + # authorize!(params[:action].to_sym, @article || Article)
89 89 #
90 90 # Call this method directly on the controller class.
91 91 #
@@ -116,18 +116,21 @@ def self.included(base)
116 116 base.helper_method :can?, :cannot?
117 117 end
118 118
119   - # Raises the CanCan::AccessDenied exception. This is often used in a
120   - # controller action to mark a request as unauthorized.
  119 + # Raises a CanCan::AccessDenied exception if the current_ability cannot
  120 + # perform the given action. This is usually called in a controller action or
  121 + # before filter to perform the authorization.
121 122 #
122 123 # def show
123 124 # @article = Article.find(params[:id])
124   - # unauthorized! if cannot? :read, @article
  125 + # authorize! :read, @article
125 126 # end
126 127 #
127   - # The unauthorized! method accepts an optional argument which sets the
128   - # message of the exception.
  128 + # A :message option can be passed to specify a different message.
129 129 #
130   - # You can rescue from the exception in the controller to define the behavior.
  130 + # authorize! :read, @article, :message => "Not authorized to read #{@article.name}"
  131 + #
  132 + # You can rescue from the exception in the controller to customize how unauthorized
  133 + # access is displayed to the user.
131 134 #
132 135 # class ApplicationController < ActionController::Base
133 136 # rescue_from CanCan::AccessDenied do |exception|
@@ -136,10 +139,20 @@ def self.included(base)
136 139 # end
137 140 # end
138 141 #
139   - # See the load_and_authorize_resource method to automatically add
140   - # the "unauthorized!" behavior to a RESTful controller's actions.
141   - def unauthorized!(message = "You are not authorized to access this page.")
142   - raise AccessDenied, message
  142 + # See the CanCan::AccessDenied exception for more details on working with the exception.
  143 + #
  144 + # See the load_and_authorize_resource method to automatically add the authorize! behavior
  145 + # to the default RESTful actions.
  146 + def authorize!(action, subject, *args)
  147 + message = nil
  148 + if args.last.kind_of?(Hash) && args.last.has_key?(:message)
  149 + message = args.pop[:message]
  150 + end
  151 + raise AccessDenied.new(message, action, subject) if cannot?(action, subject, *args)
  152 + end
  153 +
  154 + def unauthorized!(message = nil)
  155 + raise ImplementationRemoved, "The unauthorized! method has been removed from CanCan, use authorize! instead."
143 156 end
144 157
145 158 # Creates and returns the current user's ability and caches it. If you
2  lib/cancan/controller_resource.rb
... ... @@ -1,7 +1,7 @@
1 1 module CanCan
2 2 class ControllerResource # :nodoc:
3 3 def initialize(controller, name, parent = nil, options = {})
4   - raise CanCan::Error, "The :class option has been renamed to :resource for specifying the class in CanCan." if options.has_key? :class
  4 + raise ImplementationRemoved, "The :class option has been renamed to :resource for specifying the class in CanCan." if options.has_key? :class
5 5 @controller = controller
6 6 @name = name
7 7 @parent = parent
43 lib/cancan/exceptions.rb
... ... @@ -0,0 +1,43 @@
  1 +module CanCan
  2 + # A general CanCan exception
  3 + class Error < StandardError; end
  4 +
  5 + # Raised when removed code is called, an alternative solution is provided in message.
  6 + class ImplementationRemoved < Error; end
  7 +
  8 + # This error is raised when a user isn't allowed to access a given controller action.
  9 + # This usually happens within a call to ControllerAdditions#authorized! but can be
  10 + # raised manually.
  11 + #
  12 + # raise CanCan::AccessDenied.new("Not authorized!", :read, Article)
  13 + #
  14 + # The passed message, action, and subject are optional and can later be retrieved when
  15 + # rescuing from the exception.
  16 + #
  17 + # exception.message # => "Not authorized!"
  18 + # exception.action # => :read
  19 + # exception.subject # => Article
  20 + #
  21 + # If the message is not specified (or is nil) it will default to "You are anot authorized
  22 + # to access this page." This default can be overridden by setting default_message.
  23 + #
  24 + # exception.default_message = "Default error message"
  25 + # exception.message # => "Default error message"
  26 + #
  27 + # See ControllerAdditions#authorized! for more information on rescuing from this exception.
  28 + class AccessDenied < Error
  29 + attr_reader :action, :subject
  30 + attr_writer :default_message
  31 +
  32 + def initialize(message = nil, action = nil, subject = nil)
  33 + @message = message
  34 + @action = action
  35 + @subject = subject
  36 + @default_message = "You are not authorized to access this page."
  37 + end
  38 +
  39 + def to_s
  40 + @message || @default_message
  41 + end
  42 + end
  43 +end
8 lib/cancan/matchers.rb
... ... @@ -1,13 +1,13 @@
1 1 Spec::Matchers.define :be_able_to do |*args|
2   - match do |model|
3   - model.can?(*args)
  2 + match do |ability|
  3 + ability.can?(*args)
4 4 end
5 5
6   - failure_message_for_should do |model|
  6 + failure_message_for_should do |ability|
7 7 "expected to be able to #{args.map(&:inspect).join(" ")}"
8 8 end
9 9
10   - failure_message_for_should_not do |model|
  10 + failure_message_for_should_not do |ability|
11 11 "expected not to be able to #{args.map(&:inspect).join(" ")}"
12 12 end
13 13 end
2  lib/cancan/resource_authorization.rb
@@ -30,7 +30,7 @@ def load_resource
30 30 end
31 31
32 32 def authorize_resource
33   - @controller.unauthorized! if @controller.cannot?(params[:action].to_sym, resource.model_instance || resource.model_class)
  33 + @controller.authorize!(params[:action].to_sym, resource.model_instance || resource.model_class)
34 34 end
35 35
36 36 private
39 spec/cancan/controller_additions_spec.rb
@@ -5,29 +5,48 @@
5 5 @controller_class = Class.new
6 6 @controller = @controller_class.new
7 7 stub(@controller).params { {} }
  8 + stub(@controller).current_user { :current_user }
8 9 mock(@controller_class).helper_method(:can?, :cannot?)
9 10 @controller_class.send(:include, CanCan::ControllerAdditions)
10 11 end
11 12
12   - it "should raise access denied with default message when calling unauthorized!" do
13   - lambda {
14   - @controller.unauthorized!
15   - }.should raise_error(CanCan::AccessDenied, "You are not authorized to access this page.")
  13 + it "should raise ImplementationRemoved when attempting to call 'unauthorized!' on a controller" do
  14 + lambda { @controller.unauthorized! }.should raise_error(CanCan::ImplementationRemoved)
  15 + end
  16 +
  17 + it "should raise access denied exception if ability us unauthorized to perform a certain action" do
  18 + begin
  19 + @controller.authorize! :read, :foo, 1, 2, 3, :message => "Access denied!"
  20 + rescue CanCan::AccessDenied => e
  21 + e.message.should == "Access denied!"
  22 + e.action.should == :read
  23 + e.subject.should == :foo
  24 + else
  25 + fail "Expected CanCan::AccessDenied exception to be raised"
  26 + end
16 27 end
17 28
18   - it "should raise access denied with custom message when calling unauthorized!" do
19   - lambda {
20   - @controller.unauthorized! "Access denied!"
21   - }.should raise_error(CanCan::AccessDenied, "Access denied!")
  29 + it "should not raise access denied exception if ability is authorized to perform an action" do
  30 + @controller.current_ability.can :read, :foo
  31 + lambda { @controller.authorize!(:read, :foo) }.should_not raise_error
  32 + end
  33 +
  34 + it "should raise access denied exception with default message if not specified" do
  35 + begin
  36 + @controller.authorize! :read, :foo
  37 + rescue CanCan::AccessDenied => e
  38 + e.default_message = "Access denied!"
  39 + e.message.should == "Access denied!"
  40 + else
  41 + fail "Expected CanCan::AccessDenied exception to be raised"
  42 + end
22 43 end
23 44
24 45 it "should have a current_ability method which generates an ability for the current user" do
25   - stub(@controller).current_user { :current_user }
26 46 @controller.current_ability.should be_kind_of(Ability)
27 47 end
28 48
29 49 it "should provide a can? and cannot? methods which go through the current ability" do
30   - stub(@controller).current_user { :current_user }
31 50 @controller.current_ability.should be_kind_of(Ability)
32 51 @controller.can?(:foo, :bar).should be_false
33 52 @controller.cannot?(:foo, :bar).should be_true
2  spec/cancan/controller_resource_spec.rb
@@ -54,6 +54,6 @@
54 54 it "should raise an exception when specifying :class option since it is no longer used" do
55 55 lambda {
56 56 CanCan::ControllerResource.new(@controller, :ability, nil, :class => Person)
57   - }.should raise_error(CanCan::Error)
  57 + }.should raise_error(CanCan::ImplementationRemoved)
58 58 end
59 59 end
35 spec/cancan/exceptions_spec.rb
... ... @@ -0,0 +1,35 @@
  1 +require "spec_helper"
  2 +
  3 +describe CanCan::AccessDenied do
  4 + describe "with action and subject" do
  5 + before(:each) do
  6 + @exception = CanCan::AccessDenied.new(nil, :some_action, :some_subject)
  7 + end
  8 +
  9 + it "should have action and subject accessors" do
  10 + @exception.action.should == :some_action
  11 + @exception.subject.should == :some_subject
  12 + end
  13 +
  14 + it "should have a changable default message" do
  15 + @exception.message.should == "You are not authorized to access this page."
  16 + @exception.default_message = "Unauthorized!"
  17 + @exception.message.should == "Unauthorized!"
  18 + end
  19 + end
  20 +
  21 + describe "with only a message" do
  22 + before(:each) do
  23 + @exception = CanCan::AccessDenied.new("Access denied!")
  24 + end
  25 +
  26 + it "should have nil action and subject" do
  27 + @exception.action.should be_nil
  28 + @exception.subject.should be_nil
  29 + end
  30 +
  31 + it "should have passed message" do
  32 + @exception.message.should == "Access denied!"
  33 + end
  34 + end
  35 +end
13 spec/cancan/resource_authorization_spec.rb
@@ -3,7 +3,6 @@
3 3 describe CanCan::ResourceAuthorization do
4 4 before(:each) do
5 5 @controller = Object.new # simple stub for now
6   - stub(@controller).unauthorized! { raise CanCan::AccessDenied }
7 6 end
8 7
9 8 it "should load the resource into an instance variable if params[:id] is specified" do
@@ -49,19 +48,15 @@
49 48
50 49 it "should perform authorization using controller action and loaded model" do
51 50 @controller.instance_variable_set(:@ability, :some_resource)
52   - stub(@controller).cannot?(:show, :some_resource) { true }
  51 + stub(@controller).authorize!(:show, :some_resource) { raise CanCan::AccessDenied }
53 52 authorization = CanCan::ResourceAuthorization.new(@controller, :controller => "abilities", :action => "show")
54   - lambda {
55   - authorization.authorize_resource
56   - }.should raise_error(CanCan::AccessDenied)
  53 + lambda { authorization.authorize_resource }.should raise_error(CanCan::AccessDenied)
57 54 end
58 55
59 56 it "should perform authorization using controller action and non loaded model" do
60   - stub(@controller).cannot?(:show, Ability) { true }
  57 + stub(@controller).authorize!(:show, Ability) { raise CanCan::AccessDenied }
61 58 authorization = CanCan::ResourceAuthorization.new(@controller, :controller => "abilities", :action => "show")
62   - lambda {
63   - authorization.authorize_resource
64   - }.should raise_error(CanCan::AccessDenied)
  59 + lambda { authorization.authorize_resource }.should raise_error(CanCan::AccessDenied)
65 60 end
66 61
67 62 it "should call load_resource and authorize_resource for load_and_authorize_resource" do

0 comments on commit 8903fee

Please sign in to comment.
Something went wrong with that request. Please try again.