declarative_authorization 0.5.3 → 0.5.4

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG CHANGED
@@ -1,3 +1,11 @@
1
+ ** RELEASE 0.5.4 (Nov 30, 2011)
2
+
3
+ * Cumulative loading of authorization rules [Damian Curso/sb]
4
+
5
+ * Improved used_privileges rake task [urkle]
6
+
7
+ * Performance improvements [John Hawthorn]
8
+
1
9
  ** RELEASE 0.5.3 (May 25, 2011)
2
10
 
3
11
  * Bugfixes and documentation cleanup
@@ -390,10 +390,8 @@ One of three options to install the plugin:
390
390
  gem tumble
391
391
  And call from your application's root directory
392
392
  rake gems:install
393
- * Alternatively, to install from github, execute in your application's root directory
393
+ * Alternativelyi, in Rails 2, to install from github, execute in your application's root directory
394
394
  cd vendor/plugins && git clone git://github.com/stffn/declarative_authorization.git
395
- * Or, download one of the released versions from Github at
396
- http://github.com/stffn/declarative_authorization/downloads
397
395
 
398
396
  Then,
399
397
  * provide the requirements as noted below,
@@ -515,10 +513,10 @@ sbartsch at tzi.org
515
513
 
516
514
  = Contributors
517
515
 
518
- Thanks to John Joseph Bachir, Eike Carls, Dennis Blöte, Kai Chen, Erik Dahlstrand,
516
+ Thanks to John Joseph Bachir, Dennis Blöte, Eike Carls, Damian Caruso, Kai Chen, Erik Dahlstrand,
519
517
  Jeroen van Dijk, Alexander Dobriakov, Sebastian Dyck, Ari Epstein, Jeremy Friesen,
520
- Tim Harper, hollownest, Daniel Kristensen, Jeremy Kleindl, Brad Langhorst, Brian Langenfeld,
521
- Georg Ledermann, Geoff Longman, Olly Lylo, Mark Mansour, Thomas Maurer, Tyler Pickett, Sharagoz,
518
+ Tim Harper, John Hawthorn, hollownest, Daniel Kristensen, Jeremy Kleindl, Brad Langhorst, Brian Langenfeld,
519
+ Georg Ledermann, Geoff Longman, Olly Lylo, Mark Mansour, Thomas Maurer, Tyler Pickett, Edward Rudd, Sharagoz,
522
520
  TJ Singleton, Mike Vincent, Joel Westerberg
523
521
 
524
522
 
@@ -13,7 +13,7 @@ module AuthorizationRulesHelper
13
13
  }
14
14
  regexps.each do |name, res|
15
15
  res.each do |re|
16
- rules.gsub!(
16
+ rules = rules.gsub(
17
17
  re.is_a?(String) ? Regexp.new("(^|[^:])\\b(#{Regexp.escape(re)})\\b") :
18
18
  (re.is_a?(Symbol) ? Regexp.new("()(:#{Regexp.escape(re.to_s)})\\b") : re),
19
19
  "\\1<span class=\"#{name}\">\\2</span>")
@@ -1,7 +1,7 @@
1
1
  # Authorization
2
2
  require File.dirname(__FILE__) + '/reader.rb'
3
3
  require "set"
4
-
4
+ require "forwardable"
5
5
 
6
6
  module Authorization
7
7
  # An exception raised if anything goes wrong in the Authorization realm
@@ -66,50 +66,50 @@ module Authorization
66
66
  # a certain privilege is granted for the current user.
67
67
  #
68
68
  class Engine
69
- attr_reader :roles, :omnipotent_roles, :role_titles, :role_descriptions, :privileges,
70
- :privilege_hierarchy, :auth_rules, :role_hierarchy, :rev_priv_hierarchy,
71
- :rev_role_hierarchy
69
+ extend Forwardable
70
+ attr_reader :reader
71
+
72
+ def_delegators :@reader, :auth_rules_reader, :privileges_reader, :load, :load!
73
+ def_delegators :auth_rules_reader, :auth_rules, :roles, :omnipotent_roles, :role_hierarchy, :role_titles, :role_descriptions
74
+ def_delegators :privileges_reader, :privileges, :privilege_hierarchy
72
75
 
73
76
  # If +reader+ is not given, a new one is created with the default
74
77
  # authorization configuration of +AUTH_DSL_FILES+. If given, may be either
75
78
  # a Reader object or a path to a configuration file.
76
79
  def initialize (reader = nil)
77
- reader = Reader::DSLReader.factory(reader || AUTH_DSL_FILES)
78
-
79
- @privileges = reader.privileges_reader.privileges
80
- # {priv => [[priv, ctx],...]}
81
- @privilege_hierarchy = reader.privileges_reader.privilege_hierarchy
82
- @auth_rules = reader.auth_rules_reader.auth_rules
83
- @roles = reader.auth_rules_reader.roles
84
- @omnipotent_roles = reader.auth_rules_reader.omnipotent_roles
85
- @role_hierarchy = reader.auth_rules_reader.role_hierarchy
86
-
87
- @role_titles = reader.auth_rules_reader.role_titles
88
- @role_descriptions = reader.auth_rules_reader.role_descriptions
89
- @reader = reader
90
-
91
- # {[priv, ctx] => [priv, ...]}
92
- @rev_priv_hierarchy = {}
93
- @privilege_hierarchy.each do |key, value|
94
- value.each do |val|
95
- @rev_priv_hierarchy[val] ||= []
96
- @rev_priv_hierarchy[val] << key
80
+ #@auth_rules = AuthorizationRuleSet.new reader.auth_rules_reader.auth_rules
81
+ @reader = Reader::DSLReader.factory(reader || AUTH_DSL_FILES)
82
+ end
83
+
84
+ def initialize_copy (from) # :nodoc:
85
+ @reader = from.reader.clone
86
+ end
87
+
88
+ # {[priv, ctx] => [priv, ...]}
89
+ def rev_priv_hierarchy
90
+ if @rev_priv_hierarchy.nil?
91
+ @rev_priv_hierarchy = {}
92
+ privilege_hierarchy.each do |key, value|
93
+ value.each do |val|
94
+ @rev_priv_hierarchy[val] ||= []
95
+ @rev_priv_hierarchy[val] << key
96
+ end
97
97
  end
98
98
  end
99
- @rev_role_hierarchy = {}
100
- @role_hierarchy.each do |higher_role, lower_roles|
101
- lower_roles.each do |role|
102
- (@rev_role_hierarchy[role] ||= []) << higher_role
99
+ @rev_priv_hierarchy
100
+ end
101
+
102
+ # {[priv, ctx] => [priv, ...]}
103
+ def rev_role_hierarchy
104
+ if @rev_role_hierarchy.nil?
105
+ @rev_role_hierarchy = {}
106
+ role_hierarchy.each do |higher_role, lower_roles|
107
+ lower_roles.each do |role|
108
+ (@rev_role_hierarchy[role] ||= []) << higher_role
109
+ end
103
110
  end
104
111
  end
105
- end
106
-
107
- def initialize_copy (from) # :nodoc:
108
- [
109
- :privileges, :privilege_hierarchy, :roles, :role_hierarchy, :role_titles,
110
- :role_descriptions, :rev_priv_hierarchy, :rev_role_hierarchy
111
- ].each {|attr| instance_variable_set(:"@#{attr}", from.send(attr).clone) }
112
- @auth_rules = from.auth_rules.collect {|rule| rule.clone}
112
+ @rev_role_hierarchy
113
113
  end
114
114
 
115
115
  # Returns true if privilege is met by the current user. Raises
@@ -130,13 +130,17 @@ module Authorization
130
130
  # [:+user+]
131
131
  # The user to check the authorization for.
132
132
  # Defaults to Authorization#current_user.
133
+ # [:+bang+]
134
+ # Should NotAuthorized exceptions be raised
135
+ # Defaults to true.
133
136
  #
134
137
  def permit! (privilege, options = {})
135
138
  return true if Authorization.ignore_access_control
136
139
  options = {
137
140
  :object => nil,
138
141
  :skip_attribute_test => false,
139
- :context => nil
142
+ :context => nil,
143
+ :bang => true
140
144
  }.merge(options)
141
145
 
142
146
  # Make sure we're handling all privileges as symbols.
@@ -163,34 +167,40 @@ module Authorization
163
167
 
164
168
  user, roles, privileges = user_roles_privleges_from_options(privilege, options)
165
169
 
166
- return true if roles.is_a?(Array) and not (roles & @omnipotent_roles).empty?
170
+ return true if roles.is_a?(Array) and not (roles & omnipotent_roles).empty?
167
171
 
168
172
  # find a authorization rule that matches for at least one of the roles and
169
173
  # at least one of the given privileges
170
174
  attr_validator = AttributeValidator.new(self, user, options[:object], privilege, options[:context])
171
175
  rules = matching_auth_rules(roles, privileges, options[:context])
172
- if rules.empty?
173
- raise NotAuthorized, "No matching rules found for #{privilege} for #{user.inspect} " +
174
- "(roles #{roles.inspect}, privileges #{privileges.inspect}, " +
175
- "context #{options[:context].inspect})."
176
- end
177
176
 
178
177
  # Test each rule in turn to see whether any one of them is satisfied.
179
- unless rules.any? {|rule| rule.validate?(attr_validator, options[:skip_attribute_test])}
180
- raise AttributeAuthorizationError, "#{privilege} not allowed for #{user.inspect} on #{(options[:object] || options[:context]).inspect}."
178
+ rules.each do |rule|
179
+ return true if rule.validate?(attr_validator, options[:skip_attribute_test])
180
+ end
181
+
182
+ if options[:bang]
183
+ if rules.empty?
184
+ raise NotAuthorized, "No matching rules found for #{privilege} for #{user.inspect} " +
185
+ "(roles #{roles.inspect}, privileges #{privileges.inspect}, " +
186
+ "context #{options[:context].inspect})."
187
+ else
188
+ raise AttributeAuthorizationError, "#{privilege} not allowed for #{user.inspect} on #{(options[:object] || options[:context]).inspect}."
189
+ end
190
+ else
191
+ false
181
192
  end
182
- true
183
193
  end
184
194
 
185
- # Calls permit! but rescues the AuthorizationException and returns false
186
- # instead. If no exception is raised, permit? returns true and yields
187
- # to the optional block.
188
- def permit? (privilege, options = {}, &block) # :yields:
189
- permit!(privilege, options)
190
- yield if block_given?
191
- true
192
- rescue NotAuthorized
193
- false
195
+ # Calls permit! but doesn't raise authorization errors. If no exception is
196
+ # raised, permit? returns true and yields to the optional block.
197
+ def permit? (privilege, options = {}) # :yields:
198
+ if permit!(privilege, options.merge(:bang=> false))
199
+ yield if block_given?
200
+ true
201
+ else
202
+ false
203
+ end
194
204
  end
195
205
 
196
206
  # Returns the obligations to be met by the current user for the given
@@ -262,7 +272,7 @@ module Authorization
262
272
  # yet. If +dsl_file+ is given, it is passed on to Engine.new and
263
273
  # a new instance is always created.
264
274
  def self.instance (dsl_file = nil)
265
- if dsl_file or ENV['RAILS_ENV'] == 'development'
275
+ if dsl_file
266
276
  @@instance = new(dsl_file)
267
277
  else
268
278
  @@instance ||= new
@@ -303,30 +313,83 @@ module Authorization
303
313
  [user, roles, privileges]
304
314
  end
305
315
 
306
- def flatten_roles (roles)
316
+ def flatten_roles (roles, flattened_roles = Set.new)
307
317
  # TODO caching?
308
- flattened_roles = roles.dup.to_a
309
- flattened_roles.each do |role|
310
- flattened_roles.concat(@role_hierarchy[role]).uniq! if @role_hierarchy[role]
318
+ roles.reject {|role| flattened_roles.include?(role)}.each do |role|
319
+ flattened_roles << role
320
+ flatten_roles(role_hierarchy[role], flattened_roles) if role_hierarchy[role]
311
321
  end
322
+ flattened_roles.to_a
312
323
  end
313
324
 
314
325
  # Returns the privilege hierarchy flattened for given privileges in context.
315
- def flatten_privileges (privileges, context = nil)
326
+ def flatten_privileges (privileges, context = nil, flattened_privileges = Set.new)
316
327
  # TODO caching?
317
328
  raise AuthorizationUsageError, "No context given or inferable from object" unless context
318
- flattened_privileges = privileges.clone
319
- flattened_privileges.each do |priv|
320
- flattened_privileges.concat(@rev_priv_hierarchy[[priv, nil]]).uniq! if @rev_priv_hierarchy[[priv, nil]]
321
- flattened_privileges.concat(@rev_priv_hierarchy[[priv, context]]).uniq! if @rev_priv_hierarchy[[priv, context]]
329
+ privileges.reject {|priv| flattened_privileges.include?(priv)}.each do |priv|
330
+ flattened_privileges << priv
331
+ flatten_privileges(rev_priv_hierarchy[[priv, nil]], context, flattened_privileges) if rev_priv_hierarchy[[priv, nil]]
332
+ flatten_privileges(rev_priv_hierarchy[[priv, context]], context, flattened_privileges) if rev_priv_hierarchy[[priv, context]]
322
333
  end
334
+ flattened_privileges.to_a
323
335
  end
324
336
 
325
337
  def matching_auth_rules (roles, privileges, context)
326
- @auth_rules.select {|rule| rule.matches? roles, privileges, context}
338
+ auth_rules.matching(roles, privileges, context)
327
339
  end
328
340
  end
329
341
 
342
+
343
+ class AuthorizationRuleSet
344
+ include Enumerable
345
+ extend Forwardable
346
+ def_delegators :@rules, :each, :length, :[]
347
+
348
+ def initialize (rules = [])
349
+ @rules = rules.clone
350
+ reset!
351
+ end
352
+
353
+ def initialize_copy (source)
354
+ @rules = @rules.collect {|rule| rule.clone}
355
+ reset!
356
+ end
357
+
358
+ def matching(roles, privileges, context)
359
+ roles = [roles] unless roles.is_a?(Array)
360
+ rules = cached_auth_rules[context] || []
361
+ rules.select do |rule|
362
+ rule.matches? roles, privileges, context
363
+ end
364
+ end
365
+ def delete rule
366
+ @rules.delete rule
367
+ reset!
368
+ end
369
+ def << rule
370
+ @rules << rule
371
+ reset!
372
+ end
373
+ def each &block
374
+ @rules.each &block
375
+ end
376
+
377
+ private
378
+ def reset!
379
+ @cached_auth_rules =nil
380
+ end
381
+ def cached_auth_rules
382
+ return @cached_auth_rules if @cached_auth_rules
383
+ @cached_auth_rules = {}
384
+ @rules.each do |rule|
385
+ rule.contexts.each do |context|
386
+ @cached_auth_rules[context] ||= []
387
+ @cached_auth_rules[context] << rule
388
+ end
389
+ end
390
+ @cached_auth_rules
391
+ end
392
+ end
330
393
  class AuthorizationRule
331
394
  attr_reader :attributes, :contexts, :role, :privileges, :join_operator,
332
395
  :source_file, :source_line
@@ -42,35 +42,19 @@ module Authorization
42
42
  # If no object or context is specified, the controller_name is used as
43
43
  # context.
44
44
  #
45
- def permitted_to? (privilege, object_or_sym = nil, options = {}, &block)
46
- permitted_to!(privilege, object_or_sym, options.merge(:non_bang => true), &block)
45
+ def permitted_to? (privilege, object_or_sym = nil, options = {})
46
+ if authorization_engine.permit!(privilege, options_for_permit(object_or_sym, options, false))
47
+ yield if block_given?
48
+ true
49
+ else
50
+ false
51
+ end
47
52
  end
48
53
 
49
54
  # Works similar to the permitted_to? method, but
50
55
  # throws the authorization exceptions, just like Engine#permit!
51
- def permitted_to! (privilege, object_or_sym = nil, options = {}, &block)
52
- context = object = nil
53
- if object_or_sym.nil?
54
- context = self.class.decl_auth_context
55
- elsif !object_or_sym.respond_to?(:proxy_reflection) and object_or_sym.is_a?(Symbol)
56
- context = object_or_sym
57
- else
58
- object = object_or_sym
59
- end
60
-
61
- non_bang = options.delete(:non_bang)
62
- args = [
63
- privilege,
64
- {:user => current_user,
65
- :object => object,
66
- :context => context,
67
- :skip_attribute_test => object.nil?}.merge(options)
68
- ]
69
- if non_bang
70
- authorization_engine.permit?(*args, &block)
71
- else
72
- authorization_engine.permit!(*args, &block)
73
- end
56
+ def permitted_to! (privilege, object_or_sym = nil, options = {})
57
+ authorization_engine.permit!(privilege, options_for_permit(object_or_sym, options, true))
74
58
  end
75
59
 
76
60
  # While permitted_to? is used for authorization, in some cases
@@ -182,6 +166,23 @@ module Authorization
182
166
  instance_variable_set(instance_var, model_or_proxy.new)
183
167
  end
184
168
 
169
+ def options_for_permit (object_or_sym = nil, options = {}, bang = true)
170
+ context = object = nil
171
+ if object_or_sym.nil?
172
+ context = self.class.decl_auth_context
173
+ elsif !object_or_sym.respond_to?(:proxy_reflection) and object_or_sym.is_a?(Symbol)
174
+ context = object_or_sym
175
+ else
176
+ object = object_or_sym
177
+ end
178
+
179
+ {:user => current_user,
180
+ :object => object,
181
+ :context => context,
182
+ :skip_attribute_test => object.nil?,
183
+ :bang => bang}.merge(options)
184
+ end
185
+
185
186
  module ClassMethods
186
187
  #
187
188
  # Defines a filter to be applied according to the authorization of the
@@ -59,6 +59,11 @@ module Authorization
59
59
  @auth_rules_reader = AuthorizationRulesReader.new
60
60
  end
61
61
 
62
+ def initialize_copy (from) # :nodoc:
63
+ @privileges_reader = from.privileges_reader.clone
64
+ @auth_rules_reader = from.auth_rules_reader.clone
65
+ end
66
+
62
67
  # ensures you get back a DSLReader
63
68
  # if you provide a:
64
69
  # DSLReader - you will get it back.
@@ -84,17 +89,25 @@ module Authorization
84
89
  raise DSLSyntaxError, "Illegal DSL syntax: #{e}"
85
90
  end
86
91
 
87
- # Loads and parses a DSL from the given file name.
92
+ # Load and parse a DSL from the given file name.
93
+ def load (dsl_file)
94
+ parse(File.read(dsl_file), dsl_file) if File.exist?(dsl_file)
95
+ end
96
+
97
+ # Load and parse a DSL from the given file name. Raises Authorization::Reader::DSLFileNotFoundError
98
+ # if the file cannot be found.
99
+ def load! (dsl_file)
100
+ raise ::Authorization::Reader::DSLFileNotFoundError, "Error reading authorization rules file with path '#{dsl_file}'! Please ensure it exists and that it is accessible." unless File.exist?(dsl_file)
101
+ load(dsl_file)
102
+ end
103
+
104
+ # Loads and parses DSL files and returns a new reader
88
105
  def self.load (dsl_files)
89
106
  # TODO cache reader in production mode?
90
107
  reader = new
91
108
  dsl_files = [dsl_files].flatten
92
109
  dsl_files.each do |file|
93
- begin
94
- reader.parse(File.read(file), file)
95
- rescue SystemCallError
96
- raise ::Authorization::Reader::DSLFileNotFoundError, "Error reading authorization rules file with path '#{file}'! Please ensure it exists and that it is accessible."
97
- end
110
+ reader.load(file)
98
111
  end
99
112
  reader
100
113
  end
@@ -133,6 +146,11 @@ module Authorization
133
146
  @privilege_hierarchy = {}
134
147
  end
135
148
 
149
+ def initialize_copy (from) # :nodoc:
150
+ @privileges = from.privileges.clone
151
+ @privilege_hierarchy = from.privilege_hierarchy.clone
152
+ end
153
+
136
154
  def append_privilege (priv) # :nodoc:
137
155
  @privileges << priv unless @privileges.include?(priv)
138
156
  end
@@ -182,7 +200,14 @@ module Authorization
182
200
  @role_hierarchy = {}
183
201
  @role_titles = {}
184
202
  @role_descriptions = {}
185
- @auth_rules = []
203
+ @auth_rules = AuthorizationRuleSet.new
204
+ end
205
+
206
+ def initialize_copy (from) # :nodoc:
207
+ [:roles, :role_hierarchy, :auth_rules,
208
+ :role_descriptions, :role_titles, :omnipotent_roles].each do |attribute|
209
+ instance_variable_set(:"@#{attribute}", from.send(attribute).clone)
210
+ end
186
211
  end
187
212
 
188
213
  def append_role (role, options = {}) # :nodoc:
@@ -23,8 +23,15 @@ namespace :auth do
23
23
  end
24
24
  all += contr_perms.reject {|cp| cp[0].nil?}.collect {|cp| cp[0..1]}
25
25
  end
26
-
27
- model_files = `grep -l "^[[:space:]]*using_access_control" #{RAILS_ROOT}/app/models/*.rb`.split("\n")
26
+
27
+ model_all = `grep -l "Base\.using_access_control" #{RAILS_ROOT}/config/*.rb #{RAILS_ROOT}/config/initializers/*.rb`.split("\n")
28
+ if model_all.count > 0
29
+ model_files = Dir.glob( "#{RAILS_ROOT}/app/models/*.rb").reject do |item|
30
+ item.match(/_observer\.rb/)
31
+ end
32
+ else
33
+ model_files = `grep -l "^[[:space:]]*using_access_control" #{RAILS_ROOT}/app/models/*.rb`.split("\n")
34
+ end
28
35
  models_with_ac = model_files.collect {|mf| mf.sub(/^.*\//, "").sub(".rb", "").tableize.to_sym}
29
36
  model_security_privs = [:create, :read, :update, :delete]
30
37
  models_with_ac.each {|m| perms += model_security_privs.collect{|msp| [msp, m]}}
@@ -60,7 +67,7 @@ namespace :auth do
60
67
  privilege ||= :read
61
68
  end
62
69
  if privilege.nil? or context.nil?
63
- puts "Could not handle: #{ptu}"
70
+ puts "Could not handle: #{wpt}"
64
71
  else
65
72
  perms << [privilege, context]
66
73
  end
@@ -1095,10 +1095,10 @@ class AuthorizationTest < Test::Unit::TestCase
1095
1095
 
1096
1096
  engine = Authorization::Engine.new(reader)
1097
1097
  cloned_engine = engine.clone
1098
- assert_not_equal engine.auth_rules[0].contexts.object_id,
1099
- cloned_engine.auth_rules[0].contexts.object_id
1100
- assert_not_equal engine.auth_rules[0].attributes[0].send(:instance_variable_get, :@conditions_hash)[:attr].object_id,
1101
- cloned_engine.auth_rules[0].attributes[0].send(:instance_variable_get, :@conditions_hash)[:attr].object_id
1098
+ assert_not_equal engine.auth_rules.first.contexts.object_id,
1099
+ cloned_engine.auth_rules.first.contexts.object_id
1100
+ assert_not_equal engine.auth_rules.first.attributes.first.send(:instance_variable_get, :@conditions_hash)[:attr].object_id,
1101
+ cloned_engine.auth_rules.first.attributes.first.send(:instance_variable_get, :@conditions_hash)[:attr].object_id
1102
1102
  end
1103
1103
  end
1104
1104
 
@@ -171,7 +171,7 @@ class DSLReaderTest < Test::Unit::TestCase
171
171
 
172
172
  def test_load_file_not_found
173
173
  assert_raise(Authorization::Reader::DSLFileNotFoundError) do
174
- Authorization::Reader::DSLReader.load("nonexistent_file.rb")
174
+ Authorization::Reader::DSLReader.new.load!("nonexistent_file.rb")
175
175
  end
176
176
  end
177
177
  end
metadata CHANGED
@@ -5,8 +5,8 @@ version: !ruby/object:Gem::Version
5
5
  segments:
6
6
  - 0
7
7
  - 5
8
- - 3
9
- version: 0.5.3
8
+ - 4
9
+ version: 0.5.4
10
10
  platform: ruby
11
11
  authors:
12
12
  - Steffen Bartsch
@@ -14,7 +14,7 @@ autorequire:
14
14
  bindir: bin
15
15
  cert_chain: []
16
16
 
17
- date: 2011-05-25 00:00:00 +02:00
17
+ date: 2011-11-30 00:00:00 +01:00
18
18
  default_executable:
19
19
  dependencies: []
20
20