removing unauthorized! in favor of authorize! and including more information in AccessDenied exception - closes #40
This commit is contained in:
parent
ecf2818a9e
commit
8903feee70
|
@ -1,5 +1,9 @@
|
||||||
1.1.0 (not released)
|
1.1.0 (not released)
|
||||||
|
|
||||||
|
* Removing "unauthorized!" method in favor of "authorize!"
|
||||||
|
|
||||||
|
* Adding action, subject and default_message abilities to AccessDenied exception - see issue #40
|
||||||
|
|
||||||
* Adding caching to current_ability controller method, if you're overriding this be sure to add caching too.
|
* Adding caching to current_ability controller method, if you're overriding this be sure to add caching too.
|
||||||
|
|
||||||
* Adding "accessible_by" method to Active Record for fetching records matching a specific ability
|
* Adding "accessible_by" method to Active Record for fetching records matching a specific ability
|
||||||
|
|
|
@ -39,17 +39,17 @@ First, define a class called Ability in "models/ability.rb".
|
||||||
|
|
||||||
This is where all permissions will go. See the "Defining Abilities" section below for more information.
|
This is where all permissions will go. See the "Defining Abilities" section below for more information.
|
||||||
|
|
||||||
You can access the current permissions at any point using the "can?" and "cannot?" methods in the view.
|
You can access the current permissions at any point using the "can?" and "cannot?" methods in the view and controller.
|
||||||
|
|
||||||
<% if can? :update, @article %>
|
<% if can? :update, @article %>
|
||||||
<%= link_to "Edit", edit_article_path(@article) %>
|
<%= link_to "Edit", edit_article_path(@article) %>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
You can also use these methods in a controller along with the "unauthorized!" method to restrict access.
|
The "authorize!" method in the controller will raise CanCan::AccessDenied if the user is not able to perform the given action.
|
||||||
|
|
||||||
def show
|
def show
|
||||||
@article = Article.find(params[:id])
|
@article = Article.find(params[:id])
|
||||||
unauthorized! if cannot? :read, @article
|
authorize! :read, @article
|
||||||
end
|
end
|
||||||
|
|
||||||
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.
|
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
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
See the CanCan::AccessDenied rdoc for more information on exception handling.
|
||||||
|
|
||||||
|
|
||||||
== Defining Abilities
|
== Defining Abilities
|
||||||
|
|
||||||
|
|
|
@ -1,14 +1,6 @@
|
||||||
module CanCan
|
|
||||||
# A general CanCan exception
|
|
||||||
class Error < StandardError; end
|
|
||||||
|
|
||||||
# This error is raised when a user isn't allowed to access a given
|
|
||||||
# controller action. See ControllerAdditions#unauthorized! for details.
|
|
||||||
class AccessDenied < Error; end
|
|
||||||
end
|
|
||||||
|
|
||||||
require 'cancan/ability'
|
require 'cancan/ability'
|
||||||
require 'cancan/controller_resource'
|
require 'cancan/controller_resource'
|
||||||
require 'cancan/resource_authorization'
|
require 'cancan/resource_authorization'
|
||||||
require 'cancan/controller_additions'
|
require 'cancan/controller_additions'
|
||||||
require 'cancan/active_record_additions'
|
require 'cancan/active_record_additions'
|
||||||
|
require 'cancan/exceptions'
|
||||||
|
|
|
@ -85,7 +85,7 @@ module CanCan
|
||||||
# and ensure the user can perform the current action on it. Under the hood it is doing
|
# and ensure the user can perform the current action on it. Under the hood it is doing
|
||||||
# something like the following.
|
# something like the following.
|
||||||
#
|
#
|
||||||
# unauthorized! if cannot?(params[:action].to_sym, @article || Article)
|
# authorize!(params[:action].to_sym, @article || Article)
|
||||||
#
|
#
|
||||||
# Call this method directly on the controller class.
|
# Call this method directly on the controller class.
|
||||||
#
|
#
|
||||||
|
@ -116,18 +116,21 @@ module CanCan
|
||||||
base.helper_method :can?, :cannot?
|
base.helper_method :can?, :cannot?
|
||||||
end
|
end
|
||||||
|
|
||||||
# Raises the CanCan::AccessDenied exception. This is often used in a
|
# Raises a CanCan::AccessDenied exception if the current_ability cannot
|
||||||
# controller action to mark a request as unauthorized.
|
# perform the given action. This is usually called in a controller action or
|
||||||
|
# before filter to perform the authorization.
|
||||||
#
|
#
|
||||||
# def show
|
# def show
|
||||||
# @article = Article.find(params[:id])
|
# @article = Article.find(params[:id])
|
||||||
# unauthorized! if cannot? :read, @article
|
# authorize! :read, @article
|
||||||
# end
|
# end
|
||||||
#
|
#
|
||||||
# The unauthorized! method accepts an optional argument which sets the
|
# A :message option can be passed to specify a different message.
|
||||||
# message of the exception.
|
|
||||||
#
|
#
|
||||||
# You can rescue from the exception in the controller to define the behavior.
|
# authorize! :read, @article, :message => "Not authorized to read #{@article.name}"
|
||||||
|
#
|
||||||
|
# You can rescue from the exception in the controller to customize how unauthorized
|
||||||
|
# access is displayed to the user.
|
||||||
#
|
#
|
||||||
# class ApplicationController < ActionController::Base
|
# class ApplicationController < ActionController::Base
|
||||||
# rescue_from CanCan::AccessDenied do |exception|
|
# rescue_from CanCan::AccessDenied do |exception|
|
||||||
|
@ -136,10 +139,20 @@ module CanCan
|
||||||
# end
|
# end
|
||||||
# end
|
# end
|
||||||
#
|
#
|
||||||
# See the load_and_authorize_resource method to automatically add
|
# See the CanCan::AccessDenied exception for more details on working with the exception.
|
||||||
# the "unauthorized!" behavior to a RESTful controller's actions.
|
#
|
||||||
def unauthorized!(message = "You are not authorized to access this page.")
|
# See the load_and_authorize_resource method to automatically add the authorize! behavior
|
||||||
raise AccessDenied, message
|
# to the default RESTful actions.
|
||||||
|
def authorize!(action, subject, *args)
|
||||||
|
message = nil
|
||||||
|
if args.last.kind_of?(Hash) && args.last.has_key?(:message)
|
||||||
|
message = args.pop[:message]
|
||||||
|
end
|
||||||
|
raise AccessDenied.new(message, action, subject) if cannot?(action, subject, *args)
|
||||||
|
end
|
||||||
|
|
||||||
|
def unauthorized!(message = nil)
|
||||||
|
raise ImplementationRemoved, "The unauthorized! method has been removed from CanCan, use authorize! instead."
|
||||||
end
|
end
|
||||||
|
|
||||||
# Creates and returns the current user's ability and caches it. If you
|
# Creates and returns the current user's ability and caches it. If you
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
module CanCan
|
module CanCan
|
||||||
class ControllerResource # :nodoc:
|
class ControllerResource # :nodoc:
|
||||||
def initialize(controller, name, parent = nil, options = {})
|
def initialize(controller, name, parent = nil, options = {})
|
||||||
raise CanCan::Error, "The :class option has been renamed to :resource for specifying the class in CanCan." if options.has_key? :class
|
raise ImplementationRemoved, "The :class option has been renamed to :resource for specifying the class in CanCan." if options.has_key? :class
|
||||||
@controller = controller
|
@controller = controller
|
||||||
@name = name
|
@name = name
|
||||||
@parent = parent
|
@parent = parent
|
||||||
|
|
43
lib/cancan/exceptions.rb
Normal file
43
lib/cancan/exceptions.rb
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
module CanCan
|
||||||
|
# A general CanCan exception
|
||||||
|
class Error < StandardError; end
|
||||||
|
|
||||||
|
# Raised when removed code is called, an alternative solution is provided in message.
|
||||||
|
class ImplementationRemoved < 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#authorized! but can be
|
||||||
|
# raised manually.
|
||||||
|
#
|
||||||
|
# raise CanCan::AccessDenied.new("Not authorized!", :read, Article)
|
||||||
|
#
|
||||||
|
# The passed message, action, and subject are optional and can later be retrieved when
|
||||||
|
# rescuing from the exception.
|
||||||
|
#
|
||||||
|
# exception.message # => "Not authorized!"
|
||||||
|
# exception.action # => :read
|
||||||
|
# exception.subject # => Article
|
||||||
|
#
|
||||||
|
# If the message is not specified (or is nil) it will default to "You are anot authorized
|
||||||
|
# to access this page." This default can be overridden by setting default_message.
|
||||||
|
#
|
||||||
|
# exception.default_message = "Default error message"
|
||||||
|
# exception.message # => "Default error message"
|
||||||
|
#
|
||||||
|
# See ControllerAdditions#authorized! for more information on rescuing from this exception.
|
||||||
|
class AccessDenied < Error
|
||||||
|
attr_reader :action, :subject
|
||||||
|
attr_writer :default_message
|
||||||
|
|
||||||
|
def initialize(message = nil, action = nil, subject = nil)
|
||||||
|
@message = message
|
||||||
|
@action = action
|
||||||
|
@subject = subject
|
||||||
|
@default_message = "You are not authorized to access this page."
|
||||||
|
end
|
||||||
|
|
||||||
|
def to_s
|
||||||
|
@message || @default_message
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -1,13 +1,13 @@
|
||||||
Spec::Matchers.define :be_able_to do |*args|
|
Spec::Matchers.define :be_able_to do |*args|
|
||||||
match do |model|
|
match do |ability|
|
||||||
model.can?(*args)
|
ability.can?(*args)
|
||||||
end
|
end
|
||||||
|
|
||||||
failure_message_for_should do |model|
|
failure_message_for_should do |ability|
|
||||||
"expected to be able to #{args.map(&:inspect).join(" ")}"
|
"expected to be able to #{args.map(&:inspect).join(" ")}"
|
||||||
end
|
end
|
||||||
|
|
||||||
failure_message_for_should_not do |model|
|
failure_message_for_should_not do |ability|
|
||||||
"expected not to be able to #{args.map(&:inspect).join(" ")}"
|
"expected not to be able to #{args.map(&:inspect).join(" ")}"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -30,7 +30,7 @@ module CanCan
|
||||||
end
|
end
|
||||||
|
|
||||||
def authorize_resource
|
def authorize_resource
|
||||||
@controller.unauthorized! if @controller.cannot?(params[:action].to_sym, resource.model_instance || resource.model_class)
|
@controller.authorize!(params[:action].to_sym, resource.model_instance || resource.model_class)
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
|
@ -5,29 +5,48 @@ describe CanCan::ControllerAdditions do
|
||||||
@controller_class = Class.new
|
@controller_class = Class.new
|
||||||
@controller = @controller_class.new
|
@controller = @controller_class.new
|
||||||
stub(@controller).params { {} }
|
stub(@controller).params { {} }
|
||||||
|
stub(@controller).current_user { :current_user }
|
||||||
mock(@controller_class).helper_method(:can?, :cannot?)
|
mock(@controller_class).helper_method(:can?, :cannot?)
|
||||||
@controller_class.send(:include, CanCan::ControllerAdditions)
|
@controller_class.send(:include, CanCan::ControllerAdditions)
|
||||||
end
|
end
|
||||||
|
|
||||||
it "should raise access denied with default message when calling unauthorized!" do
|
it "should raise ImplementationRemoved when attempting to call 'unauthorized!' on a controller" do
|
||||||
lambda {
|
lambda { @controller.unauthorized! }.should raise_error(CanCan::ImplementationRemoved)
|
||||||
@controller.unauthorized!
|
|
||||||
}.should raise_error(CanCan::AccessDenied, "You are not authorized to access this page.")
|
|
||||||
end
|
end
|
||||||
|
|
||||||
it "should raise access denied with custom message when calling unauthorized!" do
|
it "should raise access denied exception if ability us unauthorized to perform a certain action" do
|
||||||
lambda {
|
begin
|
||||||
@controller.unauthorized! "Access denied!"
|
@controller.authorize! :read, :foo, 1, 2, 3, :message => "Access denied!"
|
||||||
}.should raise_error(CanCan::AccessDenied, "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
|
||||||
end
|
end
|
||||||
|
|
||||||
it "should have a current_ability method which generates an ability for the current user" do
|
it "should have a current_ability method which generates an ability for the current user" do
|
||||||
stub(@controller).current_user { :current_user }
|
|
||||||
@controller.current_ability.should be_kind_of(Ability)
|
@controller.current_ability.should be_kind_of(Ability)
|
||||||
end
|
end
|
||||||
|
|
||||||
it "should provide a can? and cannot? methods which go through the current ability" do
|
it "should provide a can? and cannot? methods which go through the current ability" do
|
||||||
stub(@controller).current_user { :current_user }
|
|
||||||
@controller.current_ability.should be_kind_of(Ability)
|
@controller.current_ability.should be_kind_of(Ability)
|
||||||
@controller.can?(:foo, :bar).should be_false
|
@controller.can?(:foo, :bar).should be_false
|
||||||
@controller.cannot?(:foo, :bar).should be_true
|
@controller.cannot?(:foo, :bar).should be_true
|
||||||
|
|
|
@ -54,6 +54,6 @@ describe CanCan::ControllerResource do
|
||||||
it "should raise an exception when specifying :class option since it is no longer used" do
|
it "should raise an exception when specifying :class option since it is no longer used" do
|
||||||
lambda {
|
lambda {
|
||||||
CanCan::ControllerResource.new(@controller, :ability, nil, :class => Person)
|
CanCan::ControllerResource.new(@controller, :ability, nil, :class => Person)
|
||||||
}.should raise_error(CanCan::Error)
|
}.should raise_error(CanCan::ImplementationRemoved)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
35
spec/cancan/exceptions_spec.rb
Normal file
35
spec/cancan/exceptions_spec.rb
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
require "spec_helper"
|
||||||
|
|
||||||
|
describe CanCan::AccessDenied do
|
||||||
|
describe "with action and subject" do
|
||||||
|
before(:each) do
|
||||||
|
@exception = CanCan::AccessDenied.new(nil, :some_action, :some_subject)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should have action and subject accessors" do
|
||||||
|
@exception.action.should == :some_action
|
||||||
|
@exception.subject.should == :some_subject
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should have a changable default message" do
|
||||||
|
@exception.message.should == "You are not authorized to access this page."
|
||||||
|
@exception.default_message = "Unauthorized!"
|
||||||
|
@exception.message.should == "Unauthorized!"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "with only a message" do
|
||||||
|
before(:each) do
|
||||||
|
@exception = CanCan::AccessDenied.new("Access denied!")
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should have nil action and subject" do
|
||||||
|
@exception.action.should be_nil
|
||||||
|
@exception.subject.should be_nil
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should have passed message" do
|
||||||
|
@exception.message.should == "Access denied!"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -3,7 +3,6 @@ require "spec_helper"
|
||||||
describe CanCan::ResourceAuthorization do
|
describe CanCan::ResourceAuthorization do
|
||||||
before(:each) do
|
before(:each) do
|
||||||
@controller = Object.new # simple stub for now
|
@controller = Object.new # simple stub for now
|
||||||
stub(@controller).unauthorized! { raise CanCan::AccessDenied }
|
|
||||||
end
|
end
|
||||||
|
|
||||||
it "should load the resource into an instance variable if params[:id] is specified" do
|
it "should load the resource into an instance variable if params[:id] is specified" do
|
||||||
|
@ -49,19 +48,15 @@ describe CanCan::ResourceAuthorization do
|
||||||
|
|
||||||
it "should perform authorization using controller action and loaded model" do
|
it "should perform authorization using controller action and loaded model" do
|
||||||
@controller.instance_variable_set(:@ability, :some_resource)
|
@controller.instance_variable_set(:@ability, :some_resource)
|
||||||
stub(@controller).cannot?(:show, :some_resource) { true }
|
stub(@controller).authorize!(:show, :some_resource) { raise CanCan::AccessDenied }
|
||||||
authorization = CanCan::ResourceAuthorization.new(@controller, :controller => "abilities", :action => "show")
|
authorization = CanCan::ResourceAuthorization.new(@controller, :controller => "abilities", :action => "show")
|
||||||
lambda {
|
lambda { authorization.authorize_resource }.should raise_error(CanCan::AccessDenied)
|
||||||
authorization.authorize_resource
|
|
||||||
}.should raise_error(CanCan::AccessDenied)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
it "should perform authorization using controller action and non loaded model" do
|
it "should perform authorization using controller action and non loaded model" do
|
||||||
stub(@controller).cannot?(:show, Ability) { true }
|
stub(@controller).authorize!(:show, Ability) { raise CanCan::AccessDenied }
|
||||||
authorization = CanCan::ResourceAuthorization.new(@controller, :controller => "abilities", :action => "show")
|
authorization = CanCan::ResourceAuthorization.new(@controller, :controller => "abilities", :action => "show")
|
||||||
lambda {
|
lambda { authorization.authorize_resource }.should raise_error(CanCan::AccessDenied)
|
||||||
authorization.authorize_resource
|
|
||||||
}.should raise_error(CanCan::AccessDenied)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
it "should call load_resource and authorize_resource for load_and_authorize_resource" do
|
it "should call load_resource and authorize_resource for load_and_authorize_resource" do
|
||||||
|
|
Loading…
Reference in New Issue
Block a user