Compare commits
33 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5d97cfb236 | ||
|
|
7688025404 | ||
|
|
3efa069349 | ||
|
|
b0c1646fee | ||
|
|
3f6cecbfcf | ||
|
|
fdd5ad022d | ||
|
|
3639ca90eb | ||
|
|
efa3ff1c0f | ||
|
|
9bee4a8d4b | ||
|
|
eb2826f135 | ||
|
|
a49269175e | ||
|
|
0de43c445b | ||
|
|
f9b181af05 | ||
|
|
80f1ab20fb | ||
|
|
37102fe6f8 | ||
|
|
ba999970b1 | ||
|
|
951d70e057 | ||
|
|
3a07d62782 | ||
|
|
2c2fa306cc | ||
|
|
28a9a0ac07 | ||
|
|
bcf2756ad2 | ||
|
|
c53ed1e497 | ||
|
|
07088a0cdc | ||
|
|
ff5aaf543b | ||
|
|
52435e97d9 | ||
|
|
e3eab13b86 | ||
|
|
79995e4309 | ||
|
|
8722fbc7a5 | ||
|
|
3901cbe499 | ||
|
|
471d54ce01 | ||
|
|
f23bbe04ef | ||
|
|
f1ea21b2a6 | ||
|
|
b2028c8aa7 |
24
.rvmrc
24
.rvmrc
@@ -1,23 +1 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# adapted from: http://rvm.beginrescueend.com/workflow/rvmrc/
|
||||
|
||||
ruby_string="1.8.7"
|
||||
gemset_name="cancan"
|
||||
|
||||
if rvm list strings | grep -q "${ruby_string}" ; then
|
||||
|
||||
# Load or create the specified environment
|
||||
if [[ -d "${rvm_path:-$HOME/.rvm}/environments" \
|
||||
&& -s "${rvm_path:-$HOME/.rvm}/environments/${ruby_string}@${gemset_name}" ]] ; then
|
||||
\. "${rvm_path:-$HOME/.rvm}/environments/${ruby_string}@${gemset_name}"
|
||||
else
|
||||
rvm --create "${ruby_string}@${gemset_name}"
|
||||
fi
|
||||
|
||||
else
|
||||
|
||||
# Notify the user to install the desired interpreter before proceeding.
|
||||
echo "${ruby_string} was not found, please run 'rvm install ${ruby_string}' and then cd back into the project directory."
|
||||
|
||||
fi
|
||||
rvm use 1.8.7@cancan --create
|
||||
|
||||
@@ -1,3 +1,38 @@
|
||||
1.6.2 (March 18, 2011)
|
||||
|
||||
* Fixed instance loading when :singleton option is used - see issue #310
|
||||
|
||||
|
||||
1.6.1 (March 15, 2011)
|
||||
|
||||
* Use Item.new instead of build_item for singleton resource so it doesn't effect database - see issue #304
|
||||
|
||||
* Made accessible_by action default to :index and parent action default to :show instead of :read - see issue #302
|
||||
|
||||
* Reverted Inherited Resources "collection" override since it doesn't seem to be working - see issue #305
|
||||
|
||||
|
||||
1.6.0 (March 11, 2011)
|
||||
|
||||
* Added MetaWhere support - see issue #194 and #261
|
||||
|
||||
* Allow Active Record scopes in Ability conditions - see issue #257
|
||||
|
||||
* Added :if and :unless options to check_authorization - see issue #284
|
||||
|
||||
* Several Inherited Resources fixes (thanks aq1018, tanordheim and stefanoverna)
|
||||
|
||||
* Pass action name to accessible_by call when loading a collection (thanks amw)
|
||||
|
||||
* Added :prepend option to load_and_authorize_resource to load before other filters - see issue #290
|
||||
|
||||
* Fixed spacing issue in I18n message for multi-word model names - see issue #292
|
||||
|
||||
* Load resource collection for any action which doesn't have an "id" parameter - see issue #296
|
||||
|
||||
* Raise an exception when trying to make a Ability condition with both a hash of conditions and a block - see issue #269
|
||||
|
||||
|
||||
1.5.1 (January 20, 2011)
|
||||
|
||||
* Fixing deeply nested conditions in Active Record adapter - see issue #246
|
||||
|
||||
3
Gemfile
3
Gemfile
@@ -2,9 +2,10 @@ source "http://rubygems.org"
|
||||
|
||||
case ENV["MODEL_ADAPTER"]
|
||||
when nil, "active_record"
|
||||
gem "sqlite3-ruby", :require => "sqlite3"
|
||||
gem "sqlite3"
|
||||
gem "activerecord", :require => "active_record"
|
||||
gem "with_model"
|
||||
gem "meta_where"
|
||||
when "data_mapper"
|
||||
gem "dm-core", "~> 1.0.2"
|
||||
gem "dm-sqlite-adapter", "~> 1.0.2"
|
||||
|
||||
16
README.rdoc
16
README.rdoc
@@ -70,17 +70,27 @@ If the user authorization fails, a <tt>CanCan::AccessDenied</tt> exception will
|
||||
|
||||
class ApplicationController < ActionController::Base
|
||||
rescue_from CanCan::AccessDenied do |exception|
|
||||
flash[:alert] = exception.message
|
||||
redirect_to root_url
|
||||
redirect_to root_url, :alert => exception.message
|
||||
end
|
||||
end
|
||||
|
||||
See {Exception Handling}[https://github.com/ryanb/cancan/wiki/exception-handling] for more information.
|
||||
|
||||
|
||||
=== 4. Lock It Down
|
||||
|
||||
If you want to ensure authorization happens on every action in your application, add +check_authorization+ to your ApplicationController.
|
||||
|
||||
class ApplicationController < ActionController::Base
|
||||
check_authorization
|
||||
end
|
||||
|
||||
This will raise an exception if authorization is not performed in an action. If you want to skip this add +skip_authorization_check+ to a controller subclass. See {Ensure Authorization}[https://github.com/ryanb/cancan/wiki/Ensure-Authorization] for more information.
|
||||
|
||||
|
||||
== Wiki Docs
|
||||
|
||||
* {Upgrading to 1.5}[https://github.com/ryanb/cancan/wiki/Upgrading-to-1.5]
|
||||
* {Upgrading to 1.6}[https://github.com/ryanb/cancan/wiki/Upgrading-to-1.6]
|
||||
* {Defining Abilities}[https://github.com/ryanb/cancan/wiki/Defining-Abilities]
|
||||
* {Checking Abilities}[https://github.com/ryanb/cancan/wiki/Checking-Abilities]
|
||||
* {Authorizing Controller Actions}[https://github.com/ryanb/cancan/wiki/Authorizing-Controller-Actions]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
Gem::Specification.new do |s|
|
||||
s.name = "cancan"
|
||||
s.version = "1.5.1"
|
||||
s.version = "1.6.2"
|
||||
s.author = "Ryan Bates"
|
||||
s.email = "ryan@railscasts.com"
|
||||
s.homepage = "http://github.com/ryanb/cancan"
|
||||
|
||||
@@ -206,7 +206,7 @@ module CanCan
|
||||
def unauthorized_message(action, subject)
|
||||
keys = unauthorized_message_keys(action, subject)
|
||||
variables = {:action => action.to_s}
|
||||
variables[:subject] = (subject.class == Class ? subject : subject.class).to_s.downcase
|
||||
variables[:subject] = (subject.class == Class ? subject : subject.class).to_s.underscore.humanize.downcase
|
||||
message = I18n.translate(nil, variables.merge(:scope => :unauthorized, :default => keys + [""]))
|
||||
message.blank? ? nil : message
|
||||
end
|
||||
|
||||
@@ -109,6 +109,9 @@ module CanCan
|
||||
#
|
||||
# load_resource :new => :build
|
||||
#
|
||||
# [:+prepend+]
|
||||
# Passing +true+ will use prepend_before_filter instead of a normal before_filter.
|
||||
#
|
||||
def load_resource(*args)
|
||||
cancan_resource_class.add_before_filter(self, :load_resource, *args)
|
||||
end
|
||||
@@ -162,6 +165,9 @@ module CanCan
|
||||
# [:+through+]
|
||||
# Authorize conditions on this parent resource when instance isn't available.
|
||||
#
|
||||
# [:+prepend+]
|
||||
# Passing +true+ will use prepend_before_filter instead of a normal before_filter.
|
||||
#
|
||||
def authorize_resource(*args)
|
||||
cancan_resource_class.add_before_filter(self, :authorize_resource, *args)
|
||||
end
|
||||
@@ -220,14 +226,31 @@ module CanCan
|
||||
# check_authorization
|
||||
# end
|
||||
#
|
||||
# Any arguments are passed to the +after_filter+ it triggers.
|
||||
#
|
||||
# See skip_authorization_check to bypass this check on specific controller actions.
|
||||
def check_authorization(*args)
|
||||
self.after_filter(*args) do |controller|
|
||||
unless controller.instance_variable_defined?(:@_authorized)
|
||||
raise AuthorizationNotPerformed, "This action failed the check_authorization because it does not authorize_resource. Add skip_authorization_check to bypass this check."
|
||||
end
|
||||
#
|
||||
# Options:
|
||||
# [:+only+]
|
||||
# Only applies to given actions.
|
||||
#
|
||||
# [:+except+]
|
||||
# Does not apply to given actions.
|
||||
#
|
||||
# [:+if+]
|
||||
# Supply the name of a controller method to be called. The authorization check only takes place if this returns true.
|
||||
#
|
||||
# check_authorization :if => :admin_controller?
|
||||
#
|
||||
# [:+unless+]
|
||||
# Supply the name of a controller method to be called. The authorization check only takes place if this returns false.
|
||||
#
|
||||
# check_authorization :unless => :devise_controller?
|
||||
#
|
||||
def check_authorization(options = {})
|
||||
self.after_filter(options.slice(:only, :except)) do |controller|
|
||||
return if controller.instance_variable_defined?(:@_authorized)
|
||||
return if options[:if] && !controller.send(options[:if])
|
||||
return if options[:unless] && controller.send(options[:unless])
|
||||
raise AuthorizationNotPerformed, "This action failed the check_authorization because it does not authorize_resource. Add skip_authorization_check to bypass this check."
|
||||
end
|
||||
end
|
||||
|
||||
@@ -294,8 +317,7 @@ module CanCan
|
||||
#
|
||||
# class ApplicationController < ActionController::Base
|
||||
# rescue_from CanCan::AccessDenied do |exception|
|
||||
# flash[:alert] = exception.message
|
||||
# redirect_to root_url
|
||||
# redirect_to root_url, :alert => exception.message
|
||||
# end
|
||||
# end
|
||||
#
|
||||
|
||||
@@ -5,7 +5,8 @@ module CanCan
|
||||
def self.add_before_filter(controller_class, method, *args)
|
||||
options = args.extract_options!
|
||||
resource_name = args.first
|
||||
controller_class.before_filter(options.slice(:only, :except)) do |controller|
|
||||
before_filter_method = options.delete(:prepend) ? :prepend_before_filter : :before_filter
|
||||
controller_class.send(before_filter_method, options.slice(:only, :except)) do |controller|
|
||||
controller.class.cancan_resource_class.new(controller, resource_name, options.except(:only, :except)).send(method)
|
||||
end
|
||||
end
|
||||
@@ -77,14 +78,14 @@ module CanCan
|
||||
end
|
||||
|
||||
def load_collection
|
||||
resource_base.accessible_by(current_ability)
|
||||
resource_base.accessible_by(current_ability, authorization_action)
|
||||
end
|
||||
|
||||
def build_resource
|
||||
method_name = @options[:singleton] && resource_base.respond_to?("build_#{name}") ? "build_#{name}" : "new"
|
||||
resource = resource_base.send(method_name, @params[name] || {})
|
||||
initial_attributes.each do |name, value|
|
||||
resource.send("#{name}=", value)
|
||||
resource = resource_base.new(@params[name] || {})
|
||||
resource.send("#{parent_name}=", parent_resource) if @options[:singleton] && parent_resource
|
||||
initial_attributes.each do |attr_name, value|
|
||||
resource.send("#{attr_name}=", value)
|
||||
end
|
||||
resource
|
||||
end
|
||||
@@ -96,15 +97,15 @@ module CanCan
|
||||
end
|
||||
|
||||
def find_resource
|
||||
if @options[:singleton] && resource_base.respond_to?(name)
|
||||
resource_base.send(name)
|
||||
if @options[:singleton] && parent_resource.respond_to?(name)
|
||||
parent_resource.send(name)
|
||||
else
|
||||
@options[:find_by] ? resource_base.send("find_by_#{@options[:find_by]}!", id_param) : resource_base.find(id_param)
|
||||
end
|
||||
end
|
||||
|
||||
def authorization_action
|
||||
parent? ? :read : @params[:action].to_sym
|
||||
parent? ? :show : @params[:action].to_sym
|
||||
end
|
||||
|
||||
def id_param
|
||||
@@ -112,7 +113,7 @@ module CanCan
|
||||
end
|
||||
|
||||
def member_action?
|
||||
!collection_actions.include? @params[:action].to_sym
|
||||
new_actions.include?(@params[:action].to_sym) || @options[:singleton] || (@params[:id] && !collection_actions.include?(@params[:action].to_sym))
|
||||
end
|
||||
|
||||
# Returns the class used for this resource. This can be overriden by the :class option.
|
||||
@@ -154,7 +155,7 @@ module CanCan
|
||||
def resource_base
|
||||
if @options[:through]
|
||||
if parent_resource
|
||||
@options[:singleton] ? parent_resource : parent_resource.send(@options[:through_association] || name.to_s.pluralize)
|
||||
@options[:singleton] ? resource_class : parent_resource.send(@options[:through_association] || name.to_s.pluralize)
|
||||
elsif @options[:shallow]
|
||||
resource_class
|
||||
else
|
||||
@@ -165,9 +166,13 @@ module CanCan
|
||||
end
|
||||
end
|
||||
|
||||
def parent_name
|
||||
@options[:through] && [@options[:through]].flatten.detect { |i| fetch_parent(i) }
|
||||
end
|
||||
|
||||
# The object to load this resource through.
|
||||
def parent_resource
|
||||
@options[:through] && [@options[:through]].flatten.map { |i| fetch_parent(i) }.compact.first
|
||||
parent_name && fetch_parent(parent_name)
|
||||
end
|
||||
|
||||
def fetch_parent(name)
|
||||
|
||||
@@ -3,7 +3,8 @@ module CanCan
|
||||
class InheritedResource < ControllerResource # :nodoc:
|
||||
def load_resource_instance
|
||||
if parent?
|
||||
@controller.send :parent
|
||||
@controller.send :association_chain
|
||||
@controller.instance_variable_get("@#{instance_name}")
|
||||
elsif new_actions.include? @params[:action].to_sym
|
||||
@controller.send :build_resource
|
||||
else
|
||||
|
||||
@@ -26,6 +26,17 @@ module CanCan
|
||||
raise NotImplemented, "This model adapter does not support matching on a conditions hash."
|
||||
end
|
||||
|
||||
# Used to determine if this model adapter will override the matching behavior for a specific condition.
|
||||
# If this returns true then matches_condition? will be called. See Rule#matches_conditions_hash
|
||||
def self.override_condition_matching?(subject, name, value)
|
||||
false
|
||||
end
|
||||
|
||||
# Override if override_condition_matching? returns true
|
||||
def self.matches_condition?(subject, name, value)
|
||||
raise NotImplemented, "This model adapter does not support matching on a specific condition."
|
||||
end
|
||||
|
||||
def initialize(model_class, rules)
|
||||
@model_class = model_class
|
||||
@rules = rules
|
||||
|
||||
@@ -5,6 +5,37 @@ module CanCan
|
||||
model_class <= ActiveRecord::Base
|
||||
end
|
||||
|
||||
def self.override_condition_matching?(subject, name, value)
|
||||
name.kind_of?(MetaWhere::Column) if defined? MetaWhere
|
||||
end
|
||||
|
||||
def self.matches_condition?(subject, name, value)
|
||||
subject_value = subject.send(name.column)
|
||||
if name.method.to_s.ends_with? "_any"
|
||||
value.any? { |v| meta_where_match? subject_value, name.method.to_s.sub("_any", ""), v }
|
||||
elsif name.method.to_s.ends_with? "_all"
|
||||
value.all? { |v| meta_where_match? subject_value, name.method.to_s.sub("_all", ""), v }
|
||||
else
|
||||
meta_where_match? subject_value, name.method, value
|
||||
end
|
||||
end
|
||||
|
||||
def self.meta_where_match?(subject_value, method, value)
|
||||
case method.to_sym
|
||||
when :eq then subject_value == value
|
||||
when :not_eq then subject_value != value
|
||||
when :in then value.include?(subject_value)
|
||||
when :not_in then !value.include?(subject_value)
|
||||
when :lt then subject_value < value
|
||||
when :lteq then subject_value <= value
|
||||
when :gt then subject_value > value
|
||||
when :gteq then subject_value >= value
|
||||
when :matches then subject_value =~ Regexp.new("^" + Regexp.escape(value).gsub("%", ".*") + "$", true)
|
||||
when :does_not_match then !meta_where_match?(subject_value, :matches, value)
|
||||
else raise NotImplemented, "The #{method} MetaWhere condition is not supported."
|
||||
end
|
||||
end
|
||||
|
||||
# Returns conditions intended to be used inside a database query. Normally you will not call this
|
||||
# method directly, but instead go through ModelAdditions#accessible_by.
|
||||
#
|
||||
@@ -36,7 +67,7 @@ module CanCan
|
||||
conditions.inject({}) do |result_hash, (name, value)|
|
||||
if value.kind_of? Hash
|
||||
association_class = model_class.reflect_on_association(name).class_name.constantize
|
||||
name = model_class.reflect_on_association(name).table_name
|
||||
name = model_class.reflect_on_association(name).table_name.to_sym
|
||||
value = tableized_conditions(value, association_class)
|
||||
end
|
||||
result_hash[name] = value
|
||||
@@ -55,7 +86,9 @@ module CanCan
|
||||
end
|
||||
|
||||
def database_records
|
||||
if @model_class.respond_to?(:where) && @model_class.respond_to?(:joins)
|
||||
if override_scope
|
||||
override_scope
|
||||
elsif @model_class.respond_to?(:where) && @model_class.respond_to?(:joins)
|
||||
@model_class.where(conditions).joins(joins)
|
||||
else
|
||||
@model_class.scoped(:conditions => conditions, :joins => joins)
|
||||
@@ -64,6 +97,18 @@ module CanCan
|
||||
|
||||
private
|
||||
|
||||
def override_scope
|
||||
conditions = @rules.map(&:conditions).compact
|
||||
if conditions.any? { |c| c.kind_of?(ActiveRecord::Relation) }
|
||||
if conditions.size == 1
|
||||
conditions.first
|
||||
else
|
||||
rule = @rules.detect { |rule| rule.conditions.kind_of?(ActiveRecord::Relation) }
|
||||
raise Error, "Unable to merge an Active Record scope with other conditions. Instead use a hash or SQL for #{rule.actions.first} #{rule.subjects.first} ability."
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def merge_conditions(sql, conditions_hash, behavior)
|
||||
if conditions_hash.blank?
|
||||
behavior ? true_sql : false_sql
|
||||
|
||||
@@ -4,7 +4,7 @@ module CanCan
|
||||
module ModelAdditions
|
||||
module ClassMethods
|
||||
# Returns a scope which fetches only the records that the passed ability
|
||||
# can perform a given action on. The action defaults to :read. This
|
||||
# can perform a given action on. The action defaults to :index. This
|
||||
# is usually called from a controller and passed the +current_ability+.
|
||||
#
|
||||
# @articles = Article.accessible_by(current_ability)
|
||||
@@ -19,7 +19,7 @@ module CanCan
|
||||
# @articles = Article.accessible_by(current_ability, :update)
|
||||
#
|
||||
# Here only the articles which the user can update are returned.
|
||||
def accessible_by(ability, action = :read)
|
||||
def accessible_by(ability, action = :index)
|
||||
ability.model_adapter(self, action).database_records
|
||||
end
|
||||
end
|
||||
|
||||
@@ -3,7 +3,7 @@ module CanCan
|
||||
# it holds the information about a "can" call made on Ability and provides
|
||||
# helpful methods to determine permission checking and conditions hash generation.
|
||||
class Rule # :nodoc:
|
||||
attr_reader :base_behavior, :actions, :conditions
|
||||
attr_reader :base_behavior, :subjects, :actions, :conditions
|
||||
attr_writer :expanded_actions
|
||||
|
||||
# The first argument when initializing is the base_behavior which is a true/false
|
||||
@@ -11,6 +11,7 @@ module CanCan
|
||||
# and subject respectively (such as :read, @project). The third argument is a hash
|
||||
# of conditions and the last one is the block passed to the "can" call.
|
||||
def initialize(base_behavior, action, subject, conditions, block)
|
||||
raise Error, "You are not able to supply a block with a hash of conditions in #{action} #{subject} ability. Use either one." if conditions.kind_of?(Hash) && !block.nil?
|
||||
@match_all = action.nil? && subject.nil?
|
||||
@base_behavior = base_behavior
|
||||
@actions = [action].flatten
|
||||
@@ -21,7 +22,7 @@ module CanCan
|
||||
|
||||
# Matches both the subject and action, not necessarily the conditions
|
||||
def relevant?(action, subject)
|
||||
subject = subject.values.first if subject.kind_of? Hash
|
||||
subject = subject.values.first if subject.class == Hash
|
||||
@match_all || (matches_action?(action) && matches_subject?(subject))
|
||||
end
|
||||
|
||||
@@ -31,7 +32,7 @@ module CanCan
|
||||
call_block_with_all(action, subject, extra_args)
|
||||
elsif @block && !subject_class?(subject)
|
||||
@block.call(subject, *extra_args)
|
||||
elsif @conditions.kind_of?(Hash) && subject.kind_of?(Hash)
|
||||
elsif @conditions.kind_of?(Hash) && subject.class == Hash
|
||||
nested_subject_matches_conditions?(subject)
|
||||
elsif @conditions.kind_of?(Hash) && !subject_class?(subject)
|
||||
matches_conditions_hash?(subject)
|
||||
@@ -100,17 +101,21 @@ module CanCan
|
||||
model_adapter(subject).matches_conditions_hash? subject, conditions
|
||||
else
|
||||
conditions.all? do |name, value|
|
||||
attribute = subject.send(name)
|
||||
if value.kind_of?(Hash)
|
||||
if attribute.kind_of? Array
|
||||
attribute.any? { |element| matches_conditions_hash? element, value }
|
||||
else
|
||||
matches_conditions_hash? attribute, value
|
||||
end
|
||||
elsif value.kind_of?(Array) || value.kind_of?(Range)
|
||||
value.include? attribute
|
||||
if model_adapter(subject).override_condition_matching? subject, name, value
|
||||
model_adapter(subject).matches_condition? subject, name, value
|
||||
else
|
||||
attribute == value
|
||||
attribute = subject.send(name)
|
||||
if value.kind_of?(Hash)
|
||||
if attribute.kind_of? Array
|
||||
attribute.any? { |element| matches_conditions_hash? element, value }
|
||||
else
|
||||
matches_conditions_hash? attribute, value
|
||||
end
|
||||
elsif value.kind_of?(Array) || value.kind_of?(Range)
|
||||
value.include? attribute
|
||||
else
|
||||
attribute == value
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -291,6 +291,12 @@ describe CanCan::Ability do
|
||||
@ability.can?(:read, 123 => Range).should be_true
|
||||
end
|
||||
|
||||
it "should allow to check ability on Hash-like object" do
|
||||
class Container < Hash; end
|
||||
@ability.can :read, Container
|
||||
@ability.can?(:read, Container.new).should be_true
|
||||
end
|
||||
|
||||
it "should have initial attributes based on hash conditions of 'new' action" do
|
||||
@ability.can :manage, Range, :foo => "foo", :hash => {:skip => "hashes"}
|
||||
@ability.can :create, Range, :bar => 123, :array => %w[skip arrays]
|
||||
@@ -351,6 +357,14 @@ describe CanCan::Ability do
|
||||
@ability.model_adapter(model_class, :read).should == :adapter_instance
|
||||
end
|
||||
|
||||
it "should raise an error when attempting to use a block with a hash condition since it's not likely what they want" do
|
||||
lambda {
|
||||
@ability.can :read, Array, :published => true do
|
||||
false
|
||||
end
|
||||
}.should raise_error(CanCan::Error, "You are not able to supply a block with a hash of conditions in read Array ability. Use either one.")
|
||||
end
|
||||
|
||||
describe "unauthorized message" do
|
||||
after(:each) do
|
||||
I18n.backend = nil
|
||||
@@ -389,6 +403,7 @@ describe CanCan::Ability do
|
||||
it "should have variables for action and subject" do
|
||||
I18n.backend.store_translations :en, :unauthorized => {:manage => {:all => "%{action} %{subject}"}} # old syntax for now in case testing with old I18n
|
||||
@ability.unauthorized_message(:update, Array).should == "update array"
|
||||
@ability.unauthorized_message(:update, ArgumentError).should == "update argument error"
|
||||
@ability.unauthorized_message(:edit, 1..3).should == "edit range"
|
||||
end
|
||||
end
|
||||
|
||||
@@ -42,6 +42,11 @@ describe CanCan::ControllerAdditions do
|
||||
@controller_class.load_and_authorize_resource :project, :foo => :bar
|
||||
end
|
||||
|
||||
it "load_and_authorize_resource with :prepend should prepend the before filter" do
|
||||
mock(@controller_class).prepend_before_filter({})
|
||||
@controller_class.load_and_authorize_resource :foo => :bar, :prepend => true
|
||||
end
|
||||
|
||||
it "authorize_resource should setup a before filter which passes call to ControllerResource" do
|
||||
stub(CanCan::ControllerResource).new(@controller, nil, :foo => :bar).mock!.authorize_resource
|
||||
mock(@controller_class).before_filter(:except => :show) { |options, block| block.call(@controller) }
|
||||
@@ -61,17 +66,33 @@ describe CanCan::ControllerAdditions do
|
||||
end
|
||||
|
||||
it "check_authorization should trigger AuthorizationNotPerformed in after filter" do
|
||||
mock(@controller_class).after_filter(:some_options) { |options, block| block.call(@controller) }
|
||||
mock(@controller_class).after_filter(:only => [:test]) { |options, block| block.call(@controller) }
|
||||
lambda {
|
||||
@controller_class.check_authorization(:some_options)
|
||||
@controller_class.check_authorization(:only => [:test])
|
||||
}.should raise_error(CanCan::AuthorizationNotPerformed)
|
||||
end
|
||||
|
||||
it "check_authorization should not trigger AuthorizationNotPerformed when :if is false" do
|
||||
stub(@controller).check_auth? { false }
|
||||
mock(@controller_class).after_filter({}) { |options, block| block.call(@controller) }
|
||||
lambda {
|
||||
@controller_class.check_authorization(:if => :check_auth?)
|
||||
}.should_not raise_error(CanCan::AuthorizationNotPerformed)
|
||||
end
|
||||
|
||||
it "check_authorization should not trigger AuthorizationNotPerformed when :unless is true" do
|
||||
stub(@controller).engine_controller? { true }
|
||||
mock(@controller_class).after_filter({}) { |options, block| block.call(@controller) }
|
||||
lambda {
|
||||
@controller_class.check_authorization(:unless => :engine_controller?)
|
||||
}.should_not 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) }
|
||||
mock(@controller_class).after_filter(:only => [:test]) { |options, block| block.call(@controller) }
|
||||
lambda {
|
||||
@controller_class.check_authorization(:some_options)
|
||||
@controller_class.check_authorization(:only => [:test])
|
||||
}.should_not raise_error(CanCan::AuthorizationNotPerformed)
|
||||
end
|
||||
|
||||
|
||||
@@ -67,7 +67,7 @@ describe CanCan::ControllerResource do
|
||||
end
|
||||
|
||||
it "should build a collection when on index action when class responds to accessible_by" do
|
||||
stub(Project).accessible_by(@ability) { :found_projects }
|
||||
stub(Project).accessible_by(@ability, :index) { :found_projects }
|
||||
@params[:action] = "index"
|
||||
resource = CanCan::ControllerResource.new(@controller, :project)
|
||||
resource.load_resource
|
||||
@@ -104,13 +104,13 @@ describe CanCan::ControllerResource do
|
||||
it "should authorize parent resource in collection action" do
|
||||
@params[:action] = "index"
|
||||
@controller.instance_variable_set(:@category, :some_category)
|
||||
stub(@controller).authorize!(:read, :some_category) { raise CanCan::AccessDenied }
|
||||
stub(@controller).authorize!(:show, :some_category) { raise CanCan::AccessDenied }
|
||||
resource = CanCan::ControllerResource.new(@controller, :category, :parent => true)
|
||||
lambda { resource.authorize_resource }.should raise_error(CanCan::AccessDenied)
|
||||
end
|
||||
|
||||
it "should perform authorization using controller action and loaded model" do
|
||||
@params[:action] = "show"
|
||||
@params.merge!(:action => "show", :id => 123)
|
||||
@controller.instance_variable_set(:@project, :some_project)
|
||||
stub(@controller).authorize!(:show, :some_project) { raise CanCan::AccessDenied }
|
||||
resource = CanCan::ControllerResource.new(@controller)
|
||||
@@ -118,27 +118,36 @@ describe CanCan::ControllerResource do
|
||||
end
|
||||
|
||||
it "should perform authorization using controller action and non loaded model" do
|
||||
@params[:action] = "show"
|
||||
@params.merge!(:action => "show", :id => 123)
|
||||
stub(@controller).authorize!(:show, Project) { raise CanCan::AccessDenied }
|
||||
resource = CanCan::ControllerResource.new(@controller)
|
||||
lambda { resource.authorize_resource }.should raise_error(CanCan::AccessDenied)
|
||||
end
|
||||
|
||||
it "should call load_resource and authorize_resource for load_and_authorize_resource" do
|
||||
@params[:action] = "show"
|
||||
@params.merge!(:action => "show", :id => 123)
|
||||
resource = CanCan::ControllerResource.new(@controller)
|
||||
mock(resource).load_resource
|
||||
mock(resource).authorize_resource
|
||||
resource.load_and_authorize_resource
|
||||
end
|
||||
|
||||
it "should not build a resource when on custom collection action" do
|
||||
@params[:action] = "sort"
|
||||
it "should not build a single resource when on custom collection action even with id" do
|
||||
@params.merge!(:action => "sort", :id => 123)
|
||||
resource = CanCan::ControllerResource.new(@controller, :collection => [:sort, :list])
|
||||
resource.load_resource
|
||||
@controller.instance_variable_get(:@project).should be_nil
|
||||
end
|
||||
|
||||
it "should load a collection resource when on custom action with no id param" do
|
||||
stub(Project).accessible_by(@ability, :sort) { :found_projects }
|
||||
@params[:action] = "sort"
|
||||
resource = CanCan::ControllerResource.new(@controller)
|
||||
resource.load_resource
|
||||
@controller.instance_variable_get(:@project).should be_nil
|
||||
@controller.instance_variable_get(:@projects).should == :found_projects
|
||||
end
|
||||
|
||||
it "should build a resource when on custom new action even when params[:id] exists" do
|
||||
@params.merge!(:action => "build", :id => 123)
|
||||
stub(Project).new { :some_project }
|
||||
@@ -249,8 +258,8 @@ describe CanCan::ControllerResource do
|
||||
@controller.instance_variable_get(:@project).should == :some_project
|
||||
end
|
||||
|
||||
it "should find record through has_one association with :singleton option" do
|
||||
@params.merge!(:action => "show")
|
||||
it "should find record through has_one association with :singleton option without id param" do
|
||||
@params.merge!(:action => "show", :id => nil)
|
||||
category = Object.new
|
||||
@controller.instance_variable_set(:@category, category)
|
||||
stub(category).project { :some_project }
|
||||
@@ -259,14 +268,14 @@ describe CanCan::ControllerResource do
|
||||
@controller.instance_variable_get(:@project).should == :some_project
|
||||
end
|
||||
|
||||
it "should build record through has_one association with :singleton option" do
|
||||
it "should not build record through has_one association with :singleton option because it can cause it to delete it in the database" do
|
||||
@params.merge!(:action => "create", :project => {:name => "foobar"})
|
||||
category = Object.new
|
||||
category = Category.new
|
||||
@controller.instance_variable_set(:@category, category)
|
||||
stub(category).build_project { |attributes| Project.new(attributes) }
|
||||
resource = CanCan::ControllerResource.new(@controller, :through => :category, :singleton => true)
|
||||
resource.load_resource
|
||||
@controller.instance_variable_get(:@project).name.should == "foobar"
|
||||
@controller.instance_variable_get(:@project).category.should == category
|
||||
end
|
||||
|
||||
it "should find record through has_one association with :singleton and :shallow options" do
|
||||
@@ -284,10 +293,10 @@ describe CanCan::ControllerResource do
|
||||
@controller.instance_variable_get(:@project).name.should == "foobar"
|
||||
end
|
||||
|
||||
it "should only authorize :read action on parent resource" do
|
||||
it "should only authorize :show action on parent resource" do
|
||||
project = Project.create!
|
||||
@params.merge!(:action => "new", :project_id => project.id)
|
||||
stub(@controller).authorize!(:read, project) { raise CanCan::AccessDenied }
|
||||
stub(@controller).authorize!(:show, project) { raise CanCan::AccessDenied }
|
||||
resource = CanCan::ControllerResource.new(@controller, :project, :parent => true)
|
||||
lambda { resource.load_and_authorize_resource }.should raise_error(CanCan::AccessDenied)
|
||||
end
|
||||
|
||||
@@ -12,7 +12,7 @@ describe CanCan::InheritedResource do
|
||||
end
|
||||
|
||||
it "show should load resource through @controller.resource" do
|
||||
@params[:action] = "show"
|
||||
@params.merge!(:action => "show", :id => 123)
|
||||
stub(@controller).resource { :project_resource }
|
||||
CanCan::InheritedResource.new(@controller).load_resource
|
||||
@controller.instance_variable_get(:@project).should == :project_resource
|
||||
@@ -25,16 +25,16 @@ describe CanCan::InheritedResource do
|
||||
@controller.instance_variable_get(:@project).should == :project_resource
|
||||
end
|
||||
|
||||
it "index should load through @controller.parent when parent" do
|
||||
it "index should load through @controller.association_chain when parent" do
|
||||
@params[:action] = "index"
|
||||
stub(@controller).parent { :project_resource }
|
||||
stub(@controller).association_chain { @controller.instance_variable_set(:@project, :project_resource) }
|
||||
CanCan::InheritedResource.new(@controller, :parent => true).load_resource
|
||||
@controller.instance_variable_get(:@project).should == :project_resource
|
||||
end
|
||||
|
||||
it "index should load through @controller.end_of_association_chain" do
|
||||
@params[:action] = "index"
|
||||
stub(Project).accessible_by(@ability) { :projects }
|
||||
stub(Project).accessible_by(@ability, :index) { :projects }
|
||||
stub(@controller).end_of_association_chain { Project }
|
||||
CanCan::InheritedResource.new(@controller).load_resource
|
||||
@controller.instance_variable_get(:@projects).should == :projects
|
||||
|
||||
@@ -19,8 +19,10 @@ if ENV["MODEL_ADAPTER"].nil? || ENV["MODEL_ADAPTER"] == "active_record"
|
||||
|
||||
with_model :article do
|
||||
table do |t|
|
||||
t.string "name"
|
||||
t.boolean "published"
|
||||
t.boolean "secret"
|
||||
t.integer "priority"
|
||||
t.integer "category_id"
|
||||
end
|
||||
model do
|
||||
@@ -110,10 +112,25 @@ if ENV["MODEL_ADAPTER"].nil? || ENV["MODEL_ADAPTER"] == "active_record"
|
||||
@ability.can :read, Article, :published => true
|
||||
@ability.can :read, Article, ["secret=?", true]
|
||||
article1 = Article.create!(:published => true, :secret => false)
|
||||
article2 = Article.create!(:published => true, :secret => true)
|
||||
article3 = Article.create!(:published => false, :secret => true)
|
||||
article4 = Article.create!(:published => false, :secret => false)
|
||||
Article.accessible_by(@ability).should == [article1, article2, article3]
|
||||
end
|
||||
|
||||
it "should allow a scope for conditions" do
|
||||
@ability.can :read, Article, Article.where(:secret => true)
|
||||
article1 = Article.create!(:secret => true)
|
||||
article2 = Article.create!(:secret => false)
|
||||
Article.accessible_by(@ability).should == [article1]
|
||||
end
|
||||
|
||||
it "should raise an exception when trying to merge scope with other conditions" do
|
||||
@ability.can :read, Article, :published => true
|
||||
@ability.can :read, Article, Article.where(:secret => true)
|
||||
lambda { Article.accessible_by(@ability) }.should raise_error(CanCan::Error, "Unable to merge an Active Record scope with other conditions. Instead use a hash or SQL for read Article ability.")
|
||||
end
|
||||
|
||||
it "should not allow to fetch records when ability with just block present" do
|
||||
@ability.can :read, Article do
|
||||
false
|
||||
@@ -199,5 +216,49 @@ if ENV["MODEL_ADAPTER"].nil? || ENV["MODEL_ADAPTER"] == "active_record"
|
||||
@ability.can :read, Article, :project => { :admin => true }
|
||||
@ability.model_adapter(Article, :read).joins.should == [:project]
|
||||
end
|
||||
|
||||
it "should restrict articles given a MetaWhere condition" do
|
||||
@ability.can :read, Article, :priority.lt => 2
|
||||
article1 = Article.create!(:priority => 1)
|
||||
article2 = Article.create!(:priority => 3)
|
||||
Article.accessible_by(@ability).should == [article1]
|
||||
@ability.should be_able_to(:read, article1)
|
||||
@ability.should_not be_able_to(:read, article2)
|
||||
end
|
||||
|
||||
it "should match any MetaWhere condition" do
|
||||
adapter = CanCan::ModelAdapters::ActiveRecordAdapter
|
||||
article1 = Article.new(:priority => 1, :name => "Hello World")
|
||||
adapter.matches_condition?(article1, :priority.eq, 1).should be_true
|
||||
adapter.matches_condition?(article1, :priority.eq, 2).should be_false
|
||||
adapter.matches_condition?(article1, :priority.eq_any, [1, 2]).should be_true
|
||||
adapter.matches_condition?(article1, :priority.eq_any, [2, 3]).should be_false
|
||||
adapter.matches_condition?(article1, :priority.eq_all, [1, 1]).should be_true
|
||||
adapter.matches_condition?(article1, :priority.eq_all, [1, 2]).should be_false
|
||||
adapter.matches_condition?(article1, :priority.ne, 2).should be_true
|
||||
adapter.matches_condition?(article1, :priority.ne, 1).should be_false
|
||||
adapter.matches_condition?(article1, :priority.in, [1, 2]).should be_true
|
||||
adapter.matches_condition?(article1, :priority.in, [2, 3]).should be_false
|
||||
adapter.matches_condition?(article1, :priority.nin, [2, 3]).should be_true
|
||||
adapter.matches_condition?(article1, :priority.nin, [1, 2]).should be_false
|
||||
adapter.matches_condition?(article1, :priority.lt, 2).should be_true
|
||||
adapter.matches_condition?(article1, :priority.lt, 1).should be_false
|
||||
adapter.matches_condition?(article1, :priority.lteq, 1).should be_true
|
||||
adapter.matches_condition?(article1, :priority.lteq, 0).should be_false
|
||||
adapter.matches_condition?(article1, :priority.gt, 0).should be_true
|
||||
adapter.matches_condition?(article1, :priority.gt, 1).should be_false
|
||||
adapter.matches_condition?(article1, :priority.gteq, 1).should be_true
|
||||
adapter.matches_condition?(article1, :priority.gteq, 2).should be_false
|
||||
adapter.matches_condition?(article1, :name.like, "%ello worl%").should be_true
|
||||
adapter.matches_condition?(article1, :name.like, "hello world").should be_true
|
||||
adapter.matches_condition?(article1, :name.like, "hello%").should be_true
|
||||
adapter.matches_condition?(article1, :name.like, "h%d").should be_true
|
||||
adapter.matches_condition?(article1, :name.like, "%helo%").should be_false
|
||||
adapter.matches_condition?(article1, :name.like, "hello").should be_false
|
||||
adapter.matches_condition?(article1, :name.like, "hello.world").should be_false
|
||||
# For some reason this is reporting "The not_matches MetaWhere condition is not supported."
|
||||
# adapter.matches_condition?(article1, :name.nlike, "%helo%").should be_true
|
||||
# adapter.matches_condition?(article1, :name.nlike, "%ello worl%").should be_false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -29,4 +29,5 @@ end
|
||||
|
||||
class Project < SuperModel::Base
|
||||
belongs_to :category
|
||||
attr_accessor :category # why doesn't SuperModel do this automatically?
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user