simple_cancan 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 081a175eb3429fa16be74dee2c7a57b5b5d6bf91
4
+ data.tar.gz: 86dc6d52dd8e69ced01b2c142a864d7dccbf16c8
5
+ SHA512:
6
+ metadata.gz: 83e050bc2577d86e994c2d1a294224bb6a0971faf5a772aef0d7efcaf14a8a9c2a4c42cedcd081fb2cdf506779d600bcc487ff24427bb9adba997684103f1f3f
7
+ data.tar.gz: 9640b98e04a5c430118807289294e5e58a321568eef48541193530d10959828b45486661d95175efb0f10d0ab220de1e62521e200933e691c38414991500ff88
@@ -0,0 +1,5 @@
1
+ require "simple_cancan/version"
2
+ require 'simple_cancan/ability'
3
+ require 'simple_cancan/rule'
4
+ require 'simple_cancan/controller_additions'
5
+ require 'simple_cancan/exceptions'
@@ -0,0 +1,260 @@
1
+ module SimpleCancan
2
+
3
+ module Ability
4
+
5
+ def can?(action, subject, *extra_args)
6
+ match = relevant_rules_for_match(action, subject).detect do |rule|
7
+ rule.matches_conditions?(action, subject, extra_args)
8
+ end
9
+ match ? match.base_behavior : false
10
+ end
11
+
12
+ # Convenience method which works the same as "can?" but returns the opposite value.
13
+ #
14
+ # cannot? :destroy, @project
15
+ #
16
+ def cannot?(*args)
17
+ !can?(*args)
18
+ end
19
+
20
+ # Defines which abilities are allowed using two arguments. The first one is the action
21
+ # you're setting the permission for, the second one is the class of object you're setting it on.
22
+ #
23
+ # can :update, Article
24
+ #
25
+ # You can pass an array for either of these parameters to match any one.
26
+ # Here the user has the ability to update or destroy both articles and comments.
27
+ #
28
+ # can [:update, :destroy], [Article, Comment]
29
+ #
30
+ # You can pass :all to match any object and :manage to match any action. Here are some examples.
31
+ #
32
+ # can :manage, :all
33
+ # can :update, :all
34
+ # can :manage, Project
35
+ #
36
+ # You can pass a hash of conditions as the third argument. Here the user can only see active projects which he owns.
37
+ #
38
+ # can :read, Project, :active => true, :user_id => user.id
39
+ #
40
+ # See ActiveRecordAdditions#accessible_by for how to use this in database queries. These conditions
41
+ # are also used for initial attributes when building a record in ControllerAdditions#load_resource.
42
+ #
43
+ # If the conditions hash does not give you enough control over defining abilities, you can use a block
44
+ # along with any Ruby code you want.
45
+ #
46
+ # can :update, Project do |project|
47
+ # project.groups.include?(user.group)
48
+ # end
49
+ #
50
+ # If the block returns true then the user has that :update ability for that project, otherwise he
51
+ # will be denied access. The downside to using a block is that it cannot be used to generate
52
+ # conditions for database queries.
53
+ #
54
+ # You can pass custom objects into this "can" method, this is usually done with a symbol
55
+ # and is useful if a class isn't available to define permissions on.
56
+ #
57
+ # can :read, :stats
58
+ # can? :read, :stats # => true
59
+ #
60
+ # IMPORTANT: Neither a hash of conditions or a block will be used when checking permission on a class.
61
+ #
62
+ # can :update, Project, :priority => 3
63
+ # can? :update, Project # => true
64
+ #
65
+ # If you pass no arguments to +can+, the action, class, and object will be passed to the block and the
66
+ # block will always be executed. This allows you to override the full behavior if the permissions are
67
+ # defined in an external source such as the database.
68
+ #
69
+ # can do |action, object_class, object|
70
+ # # check the database and return true/false
71
+ # end
72
+ #
73
+ def can(action = nil, subject = nil, conditions = nil, &block)
74
+ rules << Rule.new(true, action, subject, conditions, block)
75
+ end
76
+
77
+ # Defines an ability which cannot be done. Accepts the same arguments as "can".
78
+ #
79
+ # can :read, :all
80
+ # cannot :read, Comment
81
+ #
82
+ # A block can be passed just like "can", however if the logic is complex it is recommended
83
+ # to use the "can" method.
84
+ #
85
+ # cannot :read, Product do |product|
86
+ # product.invisible?
87
+ # end
88
+ #
89
+ def cannot(action = nil, subject = nil, conditions = nil, &block)
90
+ rules << Rule.new(false, action, subject, conditions, block)
91
+ end
92
+
93
+ # Alias one or more actions into another one.
94
+ #
95
+ # alias_action :update, :destroy, :to => :modify
96
+ # can :modify, Comment
97
+ #
98
+ # Then :modify permission will apply to both :update and :destroy requests.
99
+ #
100
+ # can? :update, Comment # => true
101
+ # can? :destroy, Comment # => true
102
+ #
103
+ # This only works in one direction. Passing the aliased action into the "can?" call
104
+ # will not work because aliases are meant to generate more generic actions.
105
+ #
106
+ # alias_action :update, :destroy, :to => :modify
107
+ # can :update, Comment
108
+ # can? :modify, Comment # => false
109
+ #
110
+ # Unless that exact alias is used.
111
+ #
112
+ # can :modify, Comment
113
+ # can? :modify, Comment # => true
114
+ #
115
+ # The following aliases are added by default for conveniently mapping common controller actions.
116
+ #
117
+ # alias_action :index, :show, :to => :read
118
+ # alias_action :new, :to => :create
119
+ # alias_action :edit, :to => :update
120
+ #
121
+ # This way one can use params[:action] in the controller to determine the permission.
122
+ def alias_action(*args)
123
+ target = args.pop[:to]
124
+ validate_target(target)
125
+ aliased_actions[target] ||= []
126
+ aliased_actions[target] += args
127
+ end
128
+
129
+ # User shouldn't specify targets with names of real actions or it will cause Seg fault
130
+ def validate_target(target)
131
+ raise Error, "You can't specify target (#{target}) as alias because it is real action name" if aliased_actions.values.flatten.include? target
132
+ end
133
+
134
+ # Returns a hash of aliased actions. The key is the target and the value is an array of actions aliasing the key.
135
+ def aliased_actions
136
+ @aliased_actions ||= default_alias_actions
137
+ end
138
+
139
+ # Removes previously aliased actions including the defaults.
140
+ def clear_aliased_actions
141
+ @aliased_actions = {}
142
+ end
143
+
144
+ def model_adapter(model_class, action)
145
+ adapter_class = ModelAdapters::AbstractAdapter.adapter_class(model_class)
146
+ adapter_class.new(model_class, relevant_rules_for_query(action, model_class))
147
+ end
148
+
149
+ # See ControllerAdditions#authorize! for documentation.
150
+ def authorize!(action, subject, *args)
151
+ message = nil
152
+ if args.last.kind_of?(Hash) && args.last.has_key?(:message)
153
+ message = args.pop[:message]
154
+ end
155
+ if cannot?(action, subject, *args)
156
+ message ||= unauthorized_message(action, subject)
157
+ raise AccessDenied.new(message, action, subject)
158
+ end
159
+ subject
160
+ end
161
+
162
+ def unauthorized_message(action, subject)
163
+ keys = unauthorized_message_keys(action, subject)
164
+ variables = {:action => action.to_s}
165
+ variables[:subject] = (subject.class == Class ? subject : subject.class).to_s.underscore.humanize.downcase
166
+ message = I18n.translate(nil, variables.merge(:scope => :unauthorized, :default => keys + [""]))
167
+ message.blank? ? nil : message
168
+ end
169
+
170
+ def attributes_for(action, subject)
171
+ attributes = {}
172
+ relevant_rules(action, subject).map do |rule|
173
+ attributes.merge!(rule.attributes_from_conditions) if rule.base_behavior
174
+ end
175
+ attributes
176
+ end
177
+
178
+ def has_block?(action, subject)
179
+ relevant_rules(action, subject).any?(&:only_block?)
180
+ end
181
+
182
+ def has_raw_sql?(action, subject)
183
+ relevant_rules(action, subject).any?(&:only_raw_sql?)
184
+ end
185
+
186
+ def merge(ability)
187
+ ability.send(:rules).each do |rule|
188
+ rules << rule.dup
189
+ end
190
+ self
191
+ end
192
+
193
+ private
194
+
195
+ def unauthorized_message_keys(action, subject)
196
+ subject = (subject.class == Class ? subject : subject.class).name.underscore unless subject.kind_of? Symbol
197
+ [subject, :all].map do |try_subject|
198
+ [aliases_for_action(action), :manage].flatten.map do |try_action|
199
+ :"#{try_action}.#{try_subject}"
200
+ end
201
+ end.flatten
202
+ end
203
+
204
+ # Accepts an array of actions and returns an array of actions which match.
205
+ # This should be called before "matches?" and other checking methods since they
206
+ # rely on the actions to be expanded.
207
+ def expand_actions(actions)
208
+ actions.map do |action|
209
+ aliased_actions[action] ? [action, *expand_actions(aliased_actions[action])] : action
210
+ end.flatten
211
+ end
212
+
213
+ # Given an action, it will try to find all of the actions which are aliased to it.
214
+ # This does the opposite kind of lookup as expand_actions.
215
+ def aliases_for_action(action)
216
+ results = [action]
217
+ aliased_actions.each do |aliased_action, actions|
218
+ results += aliases_for_action(aliased_action) if actions.include? action
219
+ end
220
+ results
221
+ end
222
+
223
+ def rules
224
+ @rules ||= []
225
+ end
226
+
227
+ # Returns an array of Rule instances which match the action and subject
228
+ # This does not take into consideration any hash conditions or block statements
229
+ def relevant_rules(action, subject)
230
+ rules.reverse.select do |rule|
231
+ rule.expanded_actions = expand_actions(rule.actions)
232
+ rule.relevant? action, subject
233
+ end
234
+ end
235
+
236
+ def relevant_rules_for_match(action, subject)
237
+ relevant_rules(action, subject).each do |rule|
238
+ if rule.only_raw_sql?
239
+ raise Error, "The can? and cannot? call cannot be used with a raw sql 'can' definition. The checking code cannot be determined for #{action.inspect} #{subject.inspect}"
240
+ end
241
+ end
242
+ end
243
+
244
+ def relevant_rules_for_query(action, subject)
245
+ relevant_rules(action, subject).each do |rule|
246
+ if rule.only_block?
247
+ raise Error, "The accessible_by call cannot be used with a block 'can' definition. The SQL cannot be determined for #{action.inspect} #{subject.inspect}"
248
+ end
249
+ end
250
+ end
251
+
252
+ def default_alias_actions
253
+ {
254
+ :read => [:index, :show],
255
+ :create => [:new],
256
+ :update => [:edit],
257
+ }
258
+ end
259
+ end
260
+ end
@@ -0,0 +1,91 @@
1
+ module SimpleCancan
2
+
3
+ module ControllerAdditions
4
+ module ClassMethods
5
+ def load_and_authorize_resource(*args)
6
+ cancan_resource_class.add_before_filter(self, :load_and_authorize_resource, *args)
7
+ end
8
+
9
+ def load_resource(*args)
10
+ cancan_resource_class.add_before_filter(self, :load_resource, *args)
11
+ end
12
+
13
+ def authorize_resource(*args)
14
+ cancan_resource_class.add_before_filter(self, :authorize_resource, *args)
15
+ end
16
+
17
+ def skip_load_and_authorize_resource(*args)
18
+ skip_load_resource(*args)
19
+ skip_authorize_resource(*args)
20
+ end
21
+
22
+ def skip_load_resource(*args)
23
+ options = args.extract_options!
24
+ name = args.first
25
+ cancan_skipper[:load][name] = options
26
+ end
27
+
28
+ def skip_authorize_resource(*args)
29
+ options = args.extract_options!
30
+ name = args.first
31
+ cancan_skipper[:authorize][name] = options
32
+ end
33
+
34
+ def check_authorization(options = {})
35
+ self.after_filter(options.slice(:only, :except)) do |controller|
36
+ next if controller.instance_variable_defined?(:@_authorized)
37
+ next if options[:if] && !controller.send(options[:if])
38
+ next if options[:unless] && controller.send(options[:unless])
39
+ raise AuthorizationNotPerformed, "This action failed the check_authorization because it does not authorize_resource. Add skip_authorization_check to bypass this check."
40
+ end
41
+ end
42
+
43
+ def skip_authorization_check(*args)
44
+ self.before_filter(*args) do |controller|
45
+ controller.instance_variable_set(:@_authorized, true)
46
+ end
47
+ end
48
+
49
+ def skip_authorization(*args)
50
+ raise ImplementationRemoved, "The CanCan skip_authorization method has been renamed to skip_authorization_check. Please update your code."
51
+ end
52
+
53
+ def cancan_resource_class
54
+ if ancestors.map(&:to_s).include? "InheritedResources::Actions"
55
+ InheritedResource
56
+ else
57
+ ControllerResource
58
+ end
59
+ end
60
+
61
+ def cancan_skipper
62
+ @_cancan_skipper ||= {:authorize => {}, :load => {}}
63
+ end
64
+ end
65
+
66
+ def self.included(base)
67
+ base.extend ClassMethods
68
+ end
69
+
70
+ def authorize!(*args)
71
+ @_authorized = true
72
+ current_ability.authorize!(*args)
73
+ end
74
+
75
+ def unauthorized!(message = nil)
76
+ raise ImplementationRemoved, "The unauthorized! method has been removed from CanCan, use authorize! instead."
77
+ end
78
+
79
+ def current_ability
80
+ @current_ability ||= ::Ability.new(current_account)
81
+ end
82
+
83
+ def can?(*args)
84
+ current_ability.can?(*args)
85
+ end
86
+
87
+ def cannot?(*args)
88
+ current_ability.cannot?(*args)
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,50 @@
1
+ module SimpleCancan
2
+ # A general CanCan exception
3
+ class Error < StandardError; end
4
+
5
+ # Raised when behavior is not implemented, usually used in an abstract class.
6
+ class NotImplemented < Error; end
7
+
8
+ # Raised when removed code is called, an alternative solution is provided in message.
9
+ class ImplementationRemoved < Error; end
10
+
11
+ # Raised when using check_authorization without calling authorized!
12
+ class AuthorizationNotPerformed < Error; end
13
+
14
+ # This error is raised when a user isn't allowed to access a given controller action.
15
+ # This usually happens within a call to ControllerAdditions#authorize! but can be
16
+ # raised manually.
17
+ #
18
+ # raise CanCan::AccessDenied.new("Not authorized!", :read, Article)
19
+ #
20
+ # The passed message, action, and subject are optional and can later be retrieved when
21
+ # rescuing from the exception.
22
+ #
23
+ # exception.message # => "Not authorized!"
24
+ # exception.action # => :read
25
+ # exception.subject # => Article
26
+ #
27
+ # If the message is not specified (or is nil) it will default to "You are not authorized
28
+ # to access this page." This default can be overridden by setting default_message.
29
+ #
30
+ # exception.default_message = "Default error message"
31
+ # exception.message # => "Default error message"
32
+ #
33
+ # See ControllerAdditions#authorized! for more information on rescuing from this exception
34
+ # and customizing the message using I18n.
35
+ class AccessDenied < Error
36
+ attr_reader :action, :subject
37
+ attr_writer :default_message
38
+
39
+ def initialize(message = nil, action = nil, subject = nil)
40
+ @message = message
41
+ @action = action
42
+ @subject = subject
43
+ @default_message = I18n.t(:"unauthorized.default", :default => "You are not authorized to access this page.")
44
+ end
45
+
46
+ def to_s
47
+ @message || @default_message
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,147 @@
1
+ module SimpleCancan
2
+ # This class is used internally and should only be called through Ability.
3
+ # it holds the information about a "can" call made on Ability and provides
4
+ # helpful methods to determine permission checking and conditions hash generation.
5
+ class Rule # :nodoc:
6
+ attr_reader :base_behavior, :subjects, :actions, :conditions
7
+ attr_writer :expanded_actions
8
+
9
+ # The first argument when initializing is the base_behavior which is a true/false
10
+ # value. True for "can" and false for "cannot". The next two arguments are the action
11
+ # and subject respectively (such as :read, @project). The third argument is a hash
12
+ # of conditions and the last one is the block passed to the "can" call.
13
+ def initialize(base_behavior, action, subject, conditions, block)
14
+ 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?
15
+ @match_all = action.nil? && subject.nil?
16
+ @base_behavior = base_behavior
17
+ @actions = [action].flatten
18
+ @subjects = [subject].flatten
19
+ @conditions = conditions || {}
20
+ @block = block
21
+ end
22
+
23
+ # Matches both the subject and action, not necessarily the conditions
24
+ def relevant?(action, subject)
25
+ subject = subject.values.first if subject.class == Hash
26
+ @match_all || (matches_action?(action) && matches_subject?(subject))
27
+ end
28
+
29
+ # Matches the block or conditions hash
30
+ def matches_conditions?(action, subject, extra_args)
31
+ if @match_all
32
+ call_block_with_all(action, subject, extra_args)
33
+ elsif @block && !subject_class?(subject)
34
+ @block.call(subject, *extra_args)
35
+ elsif @conditions.kind_of?(Hash) && subject.class == Hash
36
+ nested_subject_matches_conditions?(subject)
37
+ elsif @conditions.kind_of?(Hash) && !subject_class?(subject)
38
+ matches_conditions_hash?(subject)
39
+ else
40
+ # Don't stop at "cannot" definitions when there are conditions.
41
+ @conditions.empty? ? true : @base_behavior
42
+ end
43
+ end
44
+
45
+ def only_block?
46
+ conditions_empty? && !@block.nil?
47
+ end
48
+
49
+ def only_raw_sql?
50
+ @block.nil? && !conditions_empty? && !@conditions.kind_of?(Hash)
51
+ end
52
+
53
+ def conditions_empty?
54
+ @conditions == {} || @conditions.nil?
55
+ end
56
+
57
+ def unmergeable?
58
+ @conditions.respond_to?(:keys) && @conditions.present? &&
59
+ (!@conditions.keys.first.kind_of? Symbol)
60
+ end
61
+
62
+ def associations_hash(conditions = @conditions)
63
+ hash = {}
64
+ conditions.map do |name, value|
65
+ hash[name] = associations_hash(value) if value.kind_of? Hash
66
+ end if conditions.kind_of? Hash
67
+ hash
68
+ end
69
+
70
+ def attributes_from_conditions
71
+ attributes = {}
72
+ @conditions.each do |key, value|
73
+ attributes[key] = value unless [Array, Range, Hash].include? value.class
74
+ end if @conditions.kind_of? Hash
75
+ attributes
76
+ end
77
+
78
+ private
79
+
80
+ def subject_class?(subject)
81
+ klass = (subject.kind_of?(Hash) ? subject.values.first : subject).class
82
+ klass == Class || klass == Module
83
+ end
84
+
85
+ def matches_action?(action)
86
+ @expanded_actions.include?(:manage) || @expanded_actions.include?(action)
87
+ end
88
+
89
+ def matches_subject?(subject)
90
+ @subjects.include?(:all) || @subjects.include?(subject) || matches_subject_class?(subject)
91
+ end
92
+
93
+ def matches_subject_class?(subject)
94
+ @subjects.any? { |sub| sub.kind_of?(Module) && (subject.kind_of?(sub) || subject.class.to_s == sub.to_s || subject.kind_of?(Module) && subject.ancestors.include?(sub)) }
95
+ end
96
+
97
+ # Checks if the given subject matches the given conditions hash.
98
+ # This behavior can be overriden by a model adapter by defining two class methods:
99
+ # override_matching_for_conditions?(subject, conditions) and
100
+ # matches_conditions_hash?(subject, conditions)
101
+ def matches_conditions_hash?(subject, conditions = @conditions)
102
+ if conditions.empty?
103
+ true
104
+ else
105
+ if model_adapter(subject).override_conditions_hash_matching? subject, conditions
106
+ model_adapter(subject).matches_conditions_hash? subject, conditions
107
+ else
108
+ conditions.all? do |name, value|
109
+ if model_adapter(subject).override_condition_matching? subject, name, value
110
+ model_adapter(subject).matches_condition? subject, name, value
111
+ else
112
+ attribute = subject.send(name)
113
+ if value.kind_of?(Hash)
114
+ if attribute.kind_of? Array
115
+ attribute.any? { |element| matches_conditions_hash? element, value }
116
+ else
117
+ !attribute.nil? && matches_conditions_hash?(attribute, value)
118
+ end
119
+ elsif !value.is_a?(String) && value.kind_of?(Enumerable)
120
+ value.include? attribute
121
+ else
122
+ attribute == value
123
+ end
124
+ end
125
+ end
126
+ end
127
+ end
128
+ end
129
+
130
+ def nested_subject_matches_conditions?(subject_hash)
131
+ parent, child = subject_hash.first
132
+ matches_conditions_hash?(parent, @conditions[parent.class.name.downcase.to_sym] || {})
133
+ end
134
+
135
+ def call_block_with_all(action, subject, extra_args)
136
+ if subject.class == Class
137
+ @block.call(action, subject, nil, *extra_args)
138
+ else
139
+ @block.call(action, subject.class, subject, *extra_args)
140
+ end
141
+ end
142
+
143
+ def model_adapter(subject)
144
+ CanCan::ModelAdapters::AbstractAdapter.adapter_class(subject_class?(subject) ? subject : subject.class)
145
+ end
146
+ end
147
+ end
@@ -0,0 +1,3 @@
1
+ module SimpleCancan
2
+ VERSION = "0.1.0"
3
+ end
metadata ADDED
@@ -0,0 +1,80 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: simple_cancan
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Tianliang Bai
8
+ - Fuhao Xu
9
+ autorequire:
10
+ bindir: exe
11
+ cert_chain: []
12
+ date: 2015-08-29 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: bundler
16
+ requirement: !ruby/object:Gem::Requirement
17
+ requirements:
18
+ - - ~>
19
+ - !ruby/object:Gem::Version
20
+ version: '1.10'
21
+ type: :development
22
+ prerelease: false
23
+ version_requirements: !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - ~>
26
+ - !ruby/object:Gem::Version
27
+ version: '1.10'
28
+ - !ruby/object:Gem::Dependency
29
+ name: rake
30
+ requirement: !ruby/object:Gem::Requirement
31
+ requirements:
32
+ - - ~>
33
+ - !ruby/object:Gem::Version
34
+ version: '10.0'
35
+ type: :development
36
+ prerelease: false
37
+ version_requirements: !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - ~>
40
+ - !ruby/object:Gem::Version
41
+ version: '10.0'
42
+ description: A simpler cancan for padrino
43
+ email:
44
+ - happybyronbai@gmail.com
45
+ - xufuhaomap@gmail.com
46
+ executables: []
47
+ extensions: []
48
+ extra_rdoc_files: []
49
+ files:
50
+ - lib/simple_cancan.rb
51
+ - lib/simple_cancan/ability.rb
52
+ - lib/simple_cancan/controller_additions.rb
53
+ - lib/simple_cancan/exceptions.rb
54
+ - lib/simple_cancan/rule.rb
55
+ - lib/simple_cancan/version.rb
56
+ homepage: https://rubygems.org/gems/simple_cancan
57
+ licenses:
58
+ - MIT
59
+ metadata: {}
60
+ post_install_message:
61
+ rdoc_options: []
62
+ require_paths:
63
+ - lib
64
+ required_ruby_version: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - '>='
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ required_rubygems_version: !ruby/object:Gem::Requirement
70
+ requirements:
71
+ - - '>='
72
+ - !ruby/object:Gem::Version
73
+ version: '0'
74
+ requirements: []
75
+ rubyforge_project:
76
+ rubygems_version: 2.4.8
77
+ signing_key:
78
+ specification_version: 4
79
+ summary: A simpler cancan for padrino
80
+ test_files: []