cancancan 1.17.0 → 3.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +5 -5
- data/cancancan.gemspec +10 -11
- data/init.rb +2 -0
- data/lib/cancan/ability/actions.rb +93 -0
- data/lib/cancan/ability/rules.rb +96 -0
- data/lib/cancan/ability/strong_parameter_support.rb +41 -0
- data/lib/cancan/ability.rb +87 -198
- data/lib/cancan/class_matcher.rb +30 -0
- data/lib/cancan/conditions_matcher.rb +147 -0
- data/lib/cancan/config.rb +101 -0
- data/lib/cancan/controller_additions.rb +13 -30
- data/lib/cancan/controller_resource.rb +33 -225
- data/lib/cancan/controller_resource_builder.rb +26 -0
- data/lib/cancan/controller_resource_finder.rb +42 -0
- data/lib/cancan/controller_resource_loader.rb +120 -0
- data/lib/cancan/controller_resource_name_finder.rb +23 -0
- data/lib/cancan/controller_resource_sanitizer.rb +32 -0
- data/lib/cancan/exceptions.rb +24 -4
- data/lib/cancan/matchers.rb +12 -1
- data/lib/cancan/model_adapters/abstract_adapter.rb +22 -1
- data/lib/cancan/model_adapters/active_record_4_adapter.rb +25 -44
- data/lib/cancan/model_adapters/active_record_5_adapter.rb +61 -0
- data/lib/cancan/model_adapters/active_record_adapter.rb +157 -83
- data/lib/cancan/model_adapters/conditions_extractor.rb +75 -0
- data/lib/cancan/model_adapters/conditions_normalizer.rb +49 -0
- data/lib/cancan/model_adapters/default_adapter.rb +2 -0
- data/lib/cancan/model_adapters/sti_normalizer.rb +47 -0
- data/lib/cancan/model_adapters/strategies/base.rb +40 -0
- data/lib/cancan/model_adapters/strategies/joined_alias_each_rule_as_exists_subquery.rb +93 -0
- data/lib/cancan/model_adapters/strategies/joined_alias_exists_subquery.rb +31 -0
- data/lib/cancan/model_adapters/strategies/left_join.rb +11 -0
- data/lib/cancan/model_adapters/strategies/subquery.rb +18 -0
- data/lib/cancan/model_additions.rb +6 -2
- data/lib/cancan/parameter_validators.rb +9 -0
- data/lib/cancan/relevant.rb +29 -0
- data/lib/cancan/rule.rb +67 -90
- data/lib/cancan/rules_compressor.rb +23 -0
- data/lib/cancan/sti_detector.rb +12 -0
- data/lib/cancan/unauthorized_message_resolver.rb +24 -0
- data/lib/cancan/version.rb +3 -1
- data/lib/cancan.rb +15 -10
- data/lib/cancancan.rb +2 -0
- data/lib/generators/cancan/ability/ability_generator.rb +3 -1
- data/lib/generators/cancan/ability/templates/ability.rb +9 -9
- metadata +64 -86
- data/.gitignore +0 -15
- data/.rspec +0 -1
- data/.rubocop.yml +0 -39
- data/.rubocop_todo.yml +0 -54
- data/.travis.yml +0 -39
- data/Appraisals +0 -105
- data/CHANGELOG.rdoc +0 -536
- data/CONTRIBUTING.md +0 -23
- data/Gemfile +0 -3
- data/LICENSE +0 -22
- data/README.md +0 -234
- data/Rakefile +0 -13
- data/gemfiles/activerecord_3.2.gemfile +0 -18
- data/gemfiles/activerecord_4.0.gemfile +0 -19
- data/gemfiles/activerecord_4.1.gemfile +0 -19
- data/gemfiles/activerecord_4.2.gemfile +0 -21
- data/gemfiles/activerecord_5.0.gemfile +0 -20
- data/gemfiles/mongoid_2.x.gemfile +0 -18
- data/gemfiles/sequel_3.x.gemfile +0 -18
- data/lib/cancan/inherited_resource.rb +0 -20
- data/lib/cancan/model_adapters/active_record_3_adapter.rb +0 -16
- data/lib/cancan/model_adapters/mongoid_adapter.rb +0 -80
- data/lib/cancan/model_adapters/sequel_adapter.rb +0 -87
- data/spec/README.rdoc +0 -27
- data/spec/cancan/ability_spec.rb +0 -553
- data/spec/cancan/controller_additions_spec.rb +0 -164
- data/spec/cancan/controller_resource_spec.rb +0 -645
- data/spec/cancan/exceptions_spec.rb +0 -58
- data/spec/cancan/inherited_resource_spec.rb +0 -71
- data/spec/cancan/matchers_spec.rb +0 -29
- data/spec/cancan/model_adapters/active_record_4_adapter_spec.rb +0 -160
- data/spec/cancan/model_adapters/active_record_adapter_spec.rb +0 -415
- data/spec/cancan/model_adapters/default_adapter_spec.rb +0 -7
- data/spec/cancan/model_adapters/mongoid_adapter_spec.rb +0 -246
- data/spec/cancan/model_adapters/sequel_adapter_spec.rb +0 -129
- data/spec/cancan/rule_spec.rb +0 -52
- data/spec/matchers.rb +0 -13
- data/spec/spec.opts +0 -2
- data/spec/spec_helper.rb +0 -27
- data/spec/support/ability.rb +0 -6
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
|
-
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: bebbba60e68460ec234fc11e8d3cf0414e578a56c0347862c673396eb917dff9
|
|
4
|
+
data.tar.gz: bb07244a17dcf45d1852cf6677864084c2f0db5630ea9b72bdcc0c6055b5c4b6
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: be9f2b03ae43651ea70a451b97a44fd6ec6e0a09ca444ddf625b91ae3815a245e0669bb80b4d3b0687ca327bc0c4fe81028f7736cb6493ef636b43a4140f4f49
|
|
7
|
+
data.tar.gz: db75441929e737d12699f57324d031e894b5d2cdbe1555451857977c42b0ef28148cc63bb718981c32ac08bafd2873623d2151ba8e98c442f217b3f4affecda9
|
data/cancancan.gemspec
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
#
|
|
1
|
+
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
lib = File.expand_path('
|
|
3
|
+
lib = File.expand_path('lib', __dir__)
|
|
4
4
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
5
5
|
require 'cancan/version'
|
|
6
6
|
|
|
@@ -10,21 +10,20 @@ Gem::Specification.new do |s|
|
|
|
10
10
|
s.authors = ['Alessandro Rodi (Renuo AG)', 'Bryan Rite', 'Ryan Bates', 'Richard Wilson']
|
|
11
11
|
s.email = 'alessandro.rodi@renuo.ch'
|
|
12
12
|
s.homepage = 'https://github.com/CanCanCommunity/cancancan'
|
|
13
|
+
s.metadata = { 'funding_uri' => 'https://github.com/sponsors/coorasse' }
|
|
13
14
|
s.summary = 'Simple authorization solution for Rails.'
|
|
14
15
|
s.description = 'Simple authorization solution for Rails. All permissions are stored in a single location.'
|
|
15
16
|
s.platform = Gem::Platform::RUBY
|
|
16
17
|
s.license = 'MIT'
|
|
17
18
|
|
|
18
|
-
s.files = `git ls-files`.split($INPUT_RECORD_SEPARATOR)
|
|
19
|
-
s.test_files = `git ls-files -- Appraisals {spec,features,gemfiles}/*`.split($INPUT_RECORD_SEPARATOR)
|
|
20
|
-
s.executables = `git ls-files -- bin/*`.split($INPUT_RECORD_SEPARATOR).map { |f| File.basename(f) }
|
|
19
|
+
s.files = `git ls-files lib init.rb cancancan.gemspec`.split($INPUT_RECORD_SEPARATOR)
|
|
21
20
|
s.require_paths = ['lib']
|
|
22
21
|
|
|
23
|
-
s.required_ruby_version = '>= 2.
|
|
22
|
+
s.required_ruby_version = '>= 2.2.0'
|
|
24
23
|
|
|
25
|
-
s.add_development_dependency '
|
|
26
|
-
s.add_development_dependency '
|
|
27
|
-
s.add_development_dependency 'rake', '~> 10.1.1'
|
|
28
|
-
s.add_development_dependency 'rspec', '~> 3.2.0'
|
|
29
|
-
s.add_development_dependency '
|
|
24
|
+
s.add_development_dependency 'appraisal', '~> 2.0', '>= 2.0.0'
|
|
25
|
+
s.add_development_dependency 'bundler', '~> 2.0'
|
|
26
|
+
s.add_development_dependency 'rake', '~> 10.1', '>= 10.1.1'
|
|
27
|
+
s.add_development_dependency 'rspec', '~> 3.2', '>= 3.2.0'
|
|
28
|
+
s.add_development_dependency 'rubocop', '~> 1.31.1'
|
|
30
29
|
end
|
data/init.rb
CHANGED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module CanCan
|
|
4
|
+
module Ability
|
|
5
|
+
module Actions
|
|
6
|
+
# Alias one or more actions into another one.
|
|
7
|
+
#
|
|
8
|
+
# alias_action :update, :destroy, :to => :modify
|
|
9
|
+
# can :modify, Comment
|
|
10
|
+
#
|
|
11
|
+
# Then :modify permission will apply to both :update and :destroy requests.
|
|
12
|
+
#
|
|
13
|
+
# can? :update, Comment # => true
|
|
14
|
+
# can? :destroy, Comment # => true
|
|
15
|
+
#
|
|
16
|
+
# This only works in one direction. Passing the aliased action into the "can?" call
|
|
17
|
+
# will not work because aliases are meant to generate more generic actions.
|
|
18
|
+
#
|
|
19
|
+
# alias_action :update, :destroy, :to => :modify
|
|
20
|
+
# can :update, Comment
|
|
21
|
+
# can? :modify, Comment # => false
|
|
22
|
+
#
|
|
23
|
+
# Unless that exact alias is used.
|
|
24
|
+
#
|
|
25
|
+
# can :modify, Comment
|
|
26
|
+
# can? :modify, Comment # => true
|
|
27
|
+
#
|
|
28
|
+
# The following aliases are added by default for conveniently mapping common controller actions.
|
|
29
|
+
#
|
|
30
|
+
# alias_action :index, :show, :to => :read
|
|
31
|
+
# alias_action :new, :to => :create
|
|
32
|
+
# alias_action :edit, :to => :update
|
|
33
|
+
#
|
|
34
|
+
# This way one can use params[:action] in the controller to determine the permission.
|
|
35
|
+
def alias_action(*args)
|
|
36
|
+
target = args.pop[:to]
|
|
37
|
+
validate_target(target)
|
|
38
|
+
aliased_actions[target] ||= []
|
|
39
|
+
aliased_actions[target] += args
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Returns a hash of aliased actions. The key is the target and the value is an array of actions aliasing the key.
|
|
43
|
+
def aliased_actions
|
|
44
|
+
@aliased_actions ||= default_alias_actions
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Removes previously aliased actions including the defaults.
|
|
48
|
+
def clear_aliased_actions
|
|
49
|
+
@aliased_actions = {}
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
private
|
|
53
|
+
|
|
54
|
+
def default_alias_actions
|
|
55
|
+
{
|
|
56
|
+
read: %i[index show],
|
|
57
|
+
create: [:new],
|
|
58
|
+
update: [:edit]
|
|
59
|
+
}
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Given an action, it will try to find all of the actions which are aliased to it.
|
|
63
|
+
# This does the opposite kind of lookup as expand_actions.
|
|
64
|
+
def aliases_for_action(action)
|
|
65
|
+
results = [action]
|
|
66
|
+
aliased_actions.each do |aliased_action, actions|
|
|
67
|
+
results += aliases_for_action(aliased_action) if actions.include? action
|
|
68
|
+
end
|
|
69
|
+
results
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def expanded_actions
|
|
73
|
+
@expanded_actions ||= {}
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# Accepts an array of actions and returns an array of actions which match.
|
|
77
|
+
# This should be called before "matches?" and other checking methods since they
|
|
78
|
+
# rely on the actions to be expanded.
|
|
79
|
+
def expand_actions(actions)
|
|
80
|
+
expanded_actions[actions] ||= begin
|
|
81
|
+
expanded = []
|
|
82
|
+
actions.each do |action|
|
|
83
|
+
expanded << action
|
|
84
|
+
if (aliases = aliased_actions[action])
|
|
85
|
+
expanded += expand_actions(aliases)
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
expanded
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module CanCan
|
|
4
|
+
module Ability
|
|
5
|
+
module Rules
|
|
6
|
+
protected
|
|
7
|
+
|
|
8
|
+
# Must be protected as an ability can merge with other abilities.
|
|
9
|
+
# This means that an ability must expose their rules with another ability.
|
|
10
|
+
def rules
|
|
11
|
+
@rules ||= []
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
private
|
|
15
|
+
|
|
16
|
+
def add_rule(rule)
|
|
17
|
+
rules << rule
|
|
18
|
+
add_rule_to_index(rule, rules.size - 1)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def add_rule_to_index(rule, position)
|
|
22
|
+
@rules_index ||= {}
|
|
23
|
+
|
|
24
|
+
subjects = rule.subjects.compact
|
|
25
|
+
subjects << :all if subjects.empty?
|
|
26
|
+
|
|
27
|
+
subjects.each do |subject|
|
|
28
|
+
@rules_index[subject] ||= []
|
|
29
|
+
@rules_index[subject] << position
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Returns an array of Rule instances which match the action and subject
|
|
34
|
+
# This does not take into consideration any hash conditions or block statements
|
|
35
|
+
def relevant_rules(action, subject)
|
|
36
|
+
return [] unless @rules
|
|
37
|
+
|
|
38
|
+
relevant = possible_relevant_rules(subject).select do |rule|
|
|
39
|
+
rule.expanded_actions = expand_actions(rule.actions)
|
|
40
|
+
rule.relevant? action, subject
|
|
41
|
+
end
|
|
42
|
+
relevant.reverse!.uniq!
|
|
43
|
+
optimize_order! relevant
|
|
44
|
+
relevant
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def possible_relevant_rules(subject)
|
|
48
|
+
if subject.is_a?(Hash)
|
|
49
|
+
rules
|
|
50
|
+
else
|
|
51
|
+
positions = @rules_index.values_at(subject, *alternative_subjects(subject))
|
|
52
|
+
positions.compact!
|
|
53
|
+
positions.flatten!
|
|
54
|
+
positions.sort!
|
|
55
|
+
positions.map { |i| @rules[i] }
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def relevant_rules_for_match(action, subject)
|
|
60
|
+
relevant_rules(action, subject).each do |rule|
|
|
61
|
+
next unless rule.only_raw_sql?
|
|
62
|
+
|
|
63
|
+
raise Error,
|
|
64
|
+
"The can? and cannot? call cannot be used with a raw sql 'can' definition. " \
|
|
65
|
+
"The checking code cannot be determined for #{action.inspect} #{subject.inspect}"
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def relevant_rules_for_query(action, subject)
|
|
70
|
+
rules = relevant_rules(action, subject).reject do |rule|
|
|
71
|
+
# reject 'cannot' rules with attributes when doing queries
|
|
72
|
+
rule.base_behavior == false && rule.attributes.present?
|
|
73
|
+
end
|
|
74
|
+
if rules.any?(&:only_block?)
|
|
75
|
+
raise Error, "The accessible_by call cannot be used with a block 'can' definition." \
|
|
76
|
+
"The SQL cannot be determined for #{action.inspect} #{subject.inspect}"
|
|
77
|
+
end
|
|
78
|
+
rules
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# Optimizes the order of the rules, so that rules with the :all subject are evaluated first.
|
|
82
|
+
def optimize_order!(rules)
|
|
83
|
+
first_can_in_group = -1
|
|
84
|
+
rules.each_with_index do |rule, i|
|
|
85
|
+
(first_can_in_group = -1) && next unless rule.base_behavior
|
|
86
|
+
(first_can_in_group = i) && next if first_can_in_group == -1
|
|
87
|
+
next unless rule.subjects == [:all]
|
|
88
|
+
|
|
89
|
+
rules[i] = rules[first_can_in_group]
|
|
90
|
+
rules[first_can_in_group] = rule
|
|
91
|
+
first_can_in_group += 1
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module CanCan
|
|
4
|
+
module Ability
|
|
5
|
+
module StrongParameterSupport
|
|
6
|
+
# Returns an array of attributes suitable for use with strong parameters
|
|
7
|
+
#
|
|
8
|
+
# Note: reversing the relevant rules is important. Normal order means that 'cannot'
|
|
9
|
+
# rules will come before 'can' rules. However, you can't remove attributes before
|
|
10
|
+
# they are added. The 'reverse' is so that attributes will be added before the
|
|
11
|
+
# 'cannot' rules remove them.
|
|
12
|
+
def permitted_attributes(action, subject)
|
|
13
|
+
relevant_rules(action, subject)
|
|
14
|
+
.reverse
|
|
15
|
+
.select { |rule| rule.matches_conditions? action, subject }
|
|
16
|
+
.each_with_object(Set.new) do |rule, set|
|
|
17
|
+
attributes = get_attributes(rule, subject)
|
|
18
|
+
# add attributes for 'can', remove them for 'cannot'
|
|
19
|
+
rule.base_behavior ? set.merge(attributes) : set.subtract(attributes)
|
|
20
|
+
end.to_a
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
private
|
|
24
|
+
|
|
25
|
+
def subject_class?(subject)
|
|
26
|
+
klass = (subject.is_a?(Hash) ? subject.values.first : subject).class
|
|
27
|
+
[Class, Module].include? klass
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def get_attributes(rule, subject)
|
|
31
|
+
klass = subject_class?(subject) ? subject : subject.class
|
|
32
|
+
# empty attributes is an 'all'
|
|
33
|
+
if rule.attributes.empty? && klass < ActiveRecord::Base
|
|
34
|
+
klass.column_names.map(&:to_sym) - Array(klass.primary_key)
|
|
35
|
+
else
|
|
36
|
+
rule.attributes
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
data/lib/cancan/ability.rb
CHANGED
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'ability/rules.rb'
|
|
4
|
+
require_relative 'ability/actions.rb'
|
|
5
|
+
require_relative 'unauthorized_message_resolver.rb'
|
|
6
|
+
require_relative 'ability/strong_parameter_support'
|
|
7
|
+
|
|
1
8
|
module CanCan
|
|
2
9
|
# This module is designed to be included into an Ability class. This will
|
|
3
10
|
# provide the "can" methods for defining and checking abilities.
|
|
@@ -15,6 +22,11 @@ module CanCan
|
|
|
15
22
|
# end
|
|
16
23
|
#
|
|
17
24
|
module Ability
|
|
25
|
+
include CanCan::Ability::Rules
|
|
26
|
+
include CanCan::Ability::Actions
|
|
27
|
+
include CanCan::UnauthorizedMessageResolver
|
|
28
|
+
include StrongParameterSupport
|
|
29
|
+
|
|
18
30
|
# Check if the user has permission to perform a given action on an object.
|
|
19
31
|
#
|
|
20
32
|
# can? :destroy, @project
|
|
@@ -59,10 +71,10 @@ module CanCan
|
|
|
59
71
|
# end
|
|
60
72
|
#
|
|
61
73
|
# Also see the RSpec Matchers to aid in testing.
|
|
62
|
-
def can?(action, subject, *extra_args)
|
|
74
|
+
def can?(action, subject, attribute = nil, *extra_args)
|
|
63
75
|
match = extract_subjects(subject).lazy.map do |a_subject|
|
|
64
76
|
relevant_rules_for_match(action, a_subject).detect do |rule|
|
|
65
|
-
rule.matches_conditions?(action, a_subject, extra_args)
|
|
77
|
+
rule.matches_conditions?(action, a_subject, attribute, *extra_args) && rule.matches_attributes?(attribute)
|
|
66
78
|
end
|
|
67
79
|
end.reject(&:nil?).first
|
|
68
80
|
match ? match.base_behavior : false
|
|
@@ -129,8 +141,8 @@ module CanCan
|
|
|
129
141
|
# # check the database and return true/false
|
|
130
142
|
# end
|
|
131
143
|
#
|
|
132
|
-
def can(action = nil, subject = nil,
|
|
133
|
-
add_rule(Rule.new(true, action, subject,
|
|
144
|
+
def can(action = nil, subject = nil, *attributes_and_conditions, &block)
|
|
145
|
+
add_rule(Rule.new(true, action, subject, *attributes_and_conditions, &block))
|
|
134
146
|
end
|
|
135
147
|
|
|
136
148
|
# Defines an ability which cannot be done. Accepts the same arguments as "can".
|
|
@@ -145,44 +157,8 @@ module CanCan
|
|
|
145
157
|
# product.invisible?
|
|
146
158
|
# end
|
|
147
159
|
#
|
|
148
|
-
def cannot(action = nil, subject = nil,
|
|
149
|
-
add_rule(Rule.new(false, action, subject,
|
|
150
|
-
end
|
|
151
|
-
|
|
152
|
-
# Alias one or more actions into another one.
|
|
153
|
-
#
|
|
154
|
-
# alias_action :update, :destroy, :to => :modify
|
|
155
|
-
# can :modify, Comment
|
|
156
|
-
#
|
|
157
|
-
# Then :modify permission will apply to both :update and :destroy requests.
|
|
158
|
-
#
|
|
159
|
-
# can? :update, Comment # => true
|
|
160
|
-
# can? :destroy, Comment # => true
|
|
161
|
-
#
|
|
162
|
-
# This only works in one direction. Passing the aliased action into the "can?" call
|
|
163
|
-
# will not work because aliases are meant to generate more generic actions.
|
|
164
|
-
#
|
|
165
|
-
# alias_action :update, :destroy, :to => :modify
|
|
166
|
-
# can :update, Comment
|
|
167
|
-
# can? :modify, Comment # => false
|
|
168
|
-
#
|
|
169
|
-
# Unless that exact alias is used.
|
|
170
|
-
#
|
|
171
|
-
# can :modify, Comment
|
|
172
|
-
# can? :modify, Comment # => true
|
|
173
|
-
#
|
|
174
|
-
# The following aliases are added by default for conveniently mapping common controller actions.
|
|
175
|
-
#
|
|
176
|
-
# alias_action :index, :show, :to => :read
|
|
177
|
-
# alias_action :new, :to => :create
|
|
178
|
-
# alias_action :edit, :to => :update
|
|
179
|
-
#
|
|
180
|
-
# This way one can use params[:action] in the controller to determine the permission.
|
|
181
|
-
def alias_action(*args)
|
|
182
|
-
target = args.pop[:to]
|
|
183
|
-
validate_target(target)
|
|
184
|
-
aliased_actions[target] ||= []
|
|
185
|
-
aliased_actions[target] += args
|
|
160
|
+
def cannot(action = nil, subject = nil, *attributes_and_conditions, &block)
|
|
161
|
+
add_rule(Rule.new(false, action, subject, *attributes_and_conditions, &block))
|
|
186
162
|
end
|
|
187
163
|
|
|
188
164
|
# User shouldn't specify targets with names of real actions or it will cause Seg fault
|
|
@@ -191,16 +167,6 @@ module CanCan
|
|
|
191
167
|
raise Error, error_message if aliased_actions.values.flatten.include? target
|
|
192
168
|
end
|
|
193
169
|
|
|
194
|
-
# Returns a hash of aliased actions. The key is the target and the value is an array of actions aliasing the key.
|
|
195
|
-
def aliased_actions
|
|
196
|
-
@aliased_actions ||= default_alias_actions
|
|
197
|
-
end
|
|
198
|
-
|
|
199
|
-
# Removes previously aliased actions including the defaults.
|
|
200
|
-
def clear_aliased_actions
|
|
201
|
-
@aliased_actions = {}
|
|
202
|
-
end
|
|
203
|
-
|
|
204
170
|
def model_adapter(model_class, action)
|
|
205
171
|
adapter_class = ModelAdapters::AbstractAdapter.adapter_class(model_class)
|
|
206
172
|
adapter_class.new(model_class, relevant_rules_for_query(action, model_class))
|
|
@@ -208,25 +174,14 @@ module CanCan
|
|
|
208
174
|
|
|
209
175
|
# See ControllerAdditions#authorize! for documentation.
|
|
210
176
|
def authorize!(action, subject, *args)
|
|
211
|
-
message = nil
|
|
212
|
-
if args.last.is_a?(Hash) && args.last.key?(:message)
|
|
213
|
-
message = args.pop[:message]
|
|
214
|
-
end
|
|
177
|
+
message = args.last.is_a?(Hash) && args.last.key?(:message) ? args.pop[:message] : nil
|
|
215
178
|
if cannot?(action, subject, *args)
|
|
216
179
|
message ||= unauthorized_message(action, subject)
|
|
217
|
-
raise AccessDenied.new(message, action, subject)
|
|
180
|
+
raise AccessDenied.new(message, action, subject, args)
|
|
218
181
|
end
|
|
219
182
|
subject
|
|
220
183
|
end
|
|
221
184
|
|
|
222
|
-
def unauthorized_message(action, subject)
|
|
223
|
-
keys = unauthorized_message_keys(action, subject)
|
|
224
|
-
variables = { action: action.to_s }
|
|
225
|
-
variables[:subject] = (subject.class == Class ? subject : subject.class).to_s.underscore.humanize.downcase
|
|
226
|
-
message = I18n.translate(nil, variables.merge(scope: :unauthorized, default: keys + ['']))
|
|
227
|
-
message.blank? ? nil : message
|
|
228
|
-
end
|
|
229
|
-
|
|
230
185
|
def attributes_for(action, subject)
|
|
231
186
|
attributes = {}
|
|
232
187
|
relevant_rules(action, subject).map do |rule|
|
|
@@ -243,10 +198,58 @@ module CanCan
|
|
|
243
198
|
relevant_rules(action, subject).any?(&:only_raw_sql?)
|
|
244
199
|
end
|
|
245
200
|
|
|
201
|
+
# Copies all rules and aliased actions of the given +CanCan::Ability+ and adds them to +self+.
|
|
202
|
+
# class ReadAbility
|
|
203
|
+
# include CanCan::Ability
|
|
204
|
+
#
|
|
205
|
+
# def initialize
|
|
206
|
+
# can :read, User
|
|
207
|
+
# alias_action :show, :index, to: :see
|
|
208
|
+
# end
|
|
209
|
+
# end
|
|
210
|
+
#
|
|
211
|
+
# class WritingAbility
|
|
212
|
+
# include CanCan::Ability
|
|
213
|
+
#
|
|
214
|
+
# def initialize
|
|
215
|
+
# can :edit, User
|
|
216
|
+
# alias_action :create, :update, to: :modify
|
|
217
|
+
# end
|
|
218
|
+
# end
|
|
219
|
+
#
|
|
220
|
+
# read_ability = ReadAbility.new
|
|
221
|
+
# read_ability.can? :edit, User.new #=> false
|
|
222
|
+
# read_ability.merge(WritingAbility.new)
|
|
223
|
+
# read_ability.can? :edit, User.new #=> true
|
|
224
|
+
# read_ability.aliased_actions #=> [:see => [:show, :index], :modify => [:create, :update]]
|
|
225
|
+
#
|
|
226
|
+
# If there are collisions when merging the +aliased_actions+, the actions on +self+ will be
|
|
227
|
+
# overwritten.
|
|
228
|
+
#
|
|
229
|
+
# class ReadAbility
|
|
230
|
+
# include CanCan::Ability
|
|
231
|
+
#
|
|
232
|
+
# def initialize
|
|
233
|
+
# alias_action :show, :index, to: :see
|
|
234
|
+
# end
|
|
235
|
+
# end
|
|
236
|
+
#
|
|
237
|
+
# class ShowAbility
|
|
238
|
+
# include CanCan::Ability
|
|
239
|
+
#
|
|
240
|
+
# def initialize
|
|
241
|
+
# alias_action :show, to: :see
|
|
242
|
+
# end
|
|
243
|
+
# end
|
|
244
|
+
#
|
|
245
|
+
# read_ability = ReadAbility.new
|
|
246
|
+
# read_ability.merge(ShowAbility)
|
|
247
|
+
# read_ability.aliased_actions #=> [:see => [:show]]
|
|
246
248
|
def merge(ability)
|
|
247
249
|
ability.rules.each do |rule|
|
|
248
250
|
add_rule(rule.dup)
|
|
249
251
|
end
|
|
252
|
+
@aliased_actions = aliased_actions.merge(ability.aliased_actions)
|
|
250
253
|
self
|
|
251
254
|
end
|
|
252
255
|
|
|
@@ -258,66 +261,36 @@ module CanCan
|
|
|
258
261
|
#
|
|
259
262
|
# Where can_hash and cannot_hash are formatted thusly:
|
|
260
263
|
# {
|
|
261
|
-
# action:
|
|
264
|
+
# action: { subject: [attributes] }
|
|
262
265
|
# }
|
|
263
266
|
def permissions
|
|
264
|
-
permissions_list = {
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
if rule.base_behavior
|
|
270
|
-
permissions_list[:can][action] ||= []
|
|
271
|
-
permissions_list[:can][action] += subjects.map(&:to_s)
|
|
272
|
-
else
|
|
273
|
-
permissions_list[:cannot][action] ||= []
|
|
274
|
-
permissions_list[:cannot][action] += subjects.map(&:to_s)
|
|
275
|
-
end
|
|
276
|
-
end
|
|
277
|
-
end
|
|
278
|
-
|
|
267
|
+
permissions_list = {
|
|
268
|
+
can: Hash.new { |actions, k1| actions[k1] = Hash.new { |subjects, k2| subjects[k2] = [] } },
|
|
269
|
+
cannot: Hash.new { |actions, k1| actions[k1] = Hash.new { |subjects, k2| subjects[k2] = [] } }
|
|
270
|
+
}
|
|
271
|
+
rules.each { |rule| extract_rule_in_permissions(permissions_list, rule) }
|
|
279
272
|
permissions_list
|
|
280
273
|
end
|
|
281
274
|
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
275
|
+
def extract_rule_in_permissions(permissions_list, rule)
|
|
276
|
+
expand_actions(rule.actions).each do |action|
|
|
277
|
+
container = rule.base_behavior ? :can : :cannot
|
|
278
|
+
rule.subjects.each do |subject|
|
|
279
|
+
permissions_list[container][action][subject.to_s] += rule.attributes
|
|
280
|
+
end
|
|
281
|
+
end
|
|
288
282
|
end
|
|
289
283
|
|
|
290
284
|
private
|
|
291
285
|
|
|
292
286
|
def unauthorized_message_keys(action, subject)
|
|
293
287
|
subject = (subject.class == Class ? subject : subject.class).name.underscore unless subject.is_a? Symbol
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
end
|
|
298
|
-
end.flatten
|
|
299
|
-
end
|
|
300
|
-
|
|
301
|
-
# Accepts an array of actions and returns an array of actions which match.
|
|
302
|
-
# This should be called before "matches?" and other checking methods since they
|
|
303
|
-
# rely on the actions to be expanded.
|
|
304
|
-
def expand_actions(actions)
|
|
305
|
-
expanded_actions[actions] ||= begin
|
|
306
|
-
expanded = []
|
|
307
|
-
actions.each do |action|
|
|
308
|
-
expanded << action
|
|
309
|
-
if (aliases = aliased_actions[action])
|
|
310
|
-
expanded += expand_actions(aliases)
|
|
311
|
-
end
|
|
312
|
-
end
|
|
313
|
-
expanded
|
|
288
|
+
aliases = aliases_for_action(action)
|
|
289
|
+
[subject, :all].product([*aliases, :manage]).map do |try_subject, try_action|
|
|
290
|
+
:"#{try_action}.#{try_subject}"
|
|
314
291
|
end
|
|
315
292
|
end
|
|
316
293
|
|
|
317
|
-
def expanded_actions
|
|
318
|
-
@expanded_actions ||= {}
|
|
319
|
-
end
|
|
320
|
-
|
|
321
294
|
# It translates to an array the subject or the hash with multiple subjects given to can?.
|
|
322
295
|
def extract_subjects(subject)
|
|
323
296
|
if subject.is_a?(Hash) && subject.key?(:any)
|
|
@@ -327,97 +300,13 @@ module CanCan
|
|
|
327
300
|
end
|
|
328
301
|
end
|
|
329
302
|
|
|
330
|
-
# Given an action, it will try to find all of the actions which are aliased to it.
|
|
331
|
-
# This does the opposite kind of lookup as expand_actions.
|
|
332
|
-
def aliases_for_action(action)
|
|
333
|
-
results = [action]
|
|
334
|
-
aliased_actions.each do |aliased_action, actions|
|
|
335
|
-
results += aliases_for_action(aliased_action) if actions.include? action
|
|
336
|
-
end
|
|
337
|
-
results
|
|
338
|
-
end
|
|
339
|
-
|
|
340
|
-
def add_rule(rule)
|
|
341
|
-
rules << rule
|
|
342
|
-
add_rule_to_index(rule, rules.size - 1)
|
|
343
|
-
end
|
|
344
|
-
|
|
345
|
-
def add_rule_to_index(rule, position)
|
|
346
|
-
@rules_index ||= Hash.new { |h, k| h[k] = [] }
|
|
347
|
-
|
|
348
|
-
subjects = rule.subjects.compact
|
|
349
|
-
subjects << :all if subjects.empty?
|
|
350
|
-
|
|
351
|
-
subjects.each do |subject|
|
|
352
|
-
@rules_index[subject] << position
|
|
353
|
-
end
|
|
354
|
-
end
|
|
355
|
-
|
|
356
303
|
def alternative_subjects(subject)
|
|
357
304
|
subject = subject.class unless subject.is_a?(Module)
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
# Returns an array of Rule instances which match the action and subject
|
|
362
|
-
# This does not take into consideration any hash conditions or block statements
|
|
363
|
-
def relevant_rules(action, subject)
|
|
364
|
-
return [] unless @rules
|
|
365
|
-
relevant = possible_relevant_rules(subject).select do |rule|
|
|
366
|
-
rule.expanded_actions = expand_actions(rule.actions)
|
|
367
|
-
rule.relevant? action, subject
|
|
368
|
-
end
|
|
369
|
-
relevant.reverse!.uniq!
|
|
370
|
-
optimize_order! relevant
|
|
371
|
-
relevant
|
|
372
|
-
end
|
|
373
|
-
|
|
374
|
-
# Optimizes the order of the rules, so that rules with the :all subject are evaluated first.
|
|
375
|
-
def optimize_order!(rules)
|
|
376
|
-
first_can_in_group = -1
|
|
377
|
-
rules.each_with_index do |rule, i|
|
|
378
|
-
(first_can_in_group = -1) && next unless rule.base_behavior
|
|
379
|
-
(first_can_in_group = i) && next if first_can_in_group == -1
|
|
380
|
-
next unless rule.subjects == [:all]
|
|
381
|
-
rules[i] = rules[first_can_in_group]
|
|
382
|
-
rules[first_can_in_group] = rule
|
|
383
|
-
first_can_in_group += 1
|
|
384
|
-
end
|
|
385
|
-
end
|
|
386
|
-
|
|
387
|
-
def possible_relevant_rules(subject)
|
|
388
|
-
if subject.is_a?(Hash)
|
|
389
|
-
rules
|
|
305
|
+
if subject.respond_to?(:subclasses) && defined?(ActiveRecord::Base) && subject < ActiveRecord::Base
|
|
306
|
+
[:all, *(subject.ancestors + subject.subclasses), subject.class.to_s]
|
|
390
307
|
else
|
|
391
|
-
|
|
392
|
-
positions.flatten!.sort!
|
|
393
|
-
positions.map { |i| @rules[i] }
|
|
308
|
+
[:all, *subject.ancestors, subject.class.to_s]
|
|
394
309
|
end
|
|
395
310
|
end
|
|
396
|
-
|
|
397
|
-
def relevant_rules_for_match(action, subject)
|
|
398
|
-
relevant_rules(action, subject).each do |rule|
|
|
399
|
-
next unless rule.only_raw_sql?
|
|
400
|
-
raise Error,
|
|
401
|
-
"The can? and cannot? call cannot be used with a raw sql 'can' definition."\
|
|
402
|
-
" The checking code cannot be determined for #{action.inspect} #{subject.inspect}"
|
|
403
|
-
end
|
|
404
|
-
end
|
|
405
|
-
|
|
406
|
-
def relevant_rules_for_query(action, subject)
|
|
407
|
-
relevant_rules(action, subject).each do |rule|
|
|
408
|
-
if rule.only_block?
|
|
409
|
-
raise Error, "The accessible_by call cannot be used with a block 'can' definition."\
|
|
410
|
-
" The SQL cannot be determined for #{action.inspect} #{subject.inspect}"
|
|
411
|
-
end
|
|
412
|
-
end
|
|
413
|
-
end
|
|
414
|
-
|
|
415
|
-
def default_alias_actions
|
|
416
|
-
{
|
|
417
|
-
read: [:index, :show],
|
|
418
|
-
create: [:new],
|
|
419
|
-
update: [:edit]
|
|
420
|
-
}
|
|
421
|
-
end
|
|
422
311
|
end
|
|
423
312
|
end
|