declarative_authorization 0.5.3 → 0.5.4

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.
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