declarative_authorization 0.4.1 → 0.5

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,18 @@
1
+
2
+ ** RELEASE 0.5 (July 21, 2010) **
3
+
4
+ * Ruby 1.9.2 compatibility [sb]
5
+
6
+ * Comparisons in authorization roles: lt, lte, gt, gte [aepstein,hollownest]
7
+
8
+ * DSL optimization: allow array being passed to to
9
+
10
+ * Omnipotent roles [timcharper]
11
+
12
+ * Meaningful error in case of missing authorization rules file [timcharper]
13
+
14
+ * Rails 3 support [sb]
15
+
1
16
  * Support shallow nested resources [jjb]
2
17
 
3
18
  * Allow multiple authorization rules files [kaichen]
data/README.rdoc CHANGED
@@ -277,8 +277,9 @@ Privilege hierarchies may be context-specific, e.g. applicable to :employees.
277
277
  privilege :manage, :employees, :includes => :increase_salary
278
278
  end
279
279
 
280
- For more complex use cases, authorizations need to be based on attributes. E.g.
281
- if a branch admin should manage only employees of his branch (see
280
+ For more complex use cases, authorizations need to be based on attributes. Note
281
+ that you then also need to set :attribute_check => true in controllers for filter_access_to.
282
+ E.g. if a branch admin should manage only employees of his branch (see
282
283
  Authorization::Reader in the API docs for a full list of available operators):
283
284
 
284
285
  authorization do
@@ -379,7 +380,7 @@ Then,
379
380
 
380
381
  == Providing the Plugin's Requirements
381
382
  The requirements are
382
- * Rails >= 2.1 and Ruby >= 1.8.6, including 1.9
383
+ * Rails >= 2.2, including 3 and Ruby >= 1.8.6, including 1.9
383
384
  * An authentication mechanism
384
385
  * A user object returned by Controller#current_user
385
386
  * An array of role symbols returned by User#role_symbols
@@ -490,10 +491,10 @@ sbartsch at tzi.org
490
491
 
491
492
  = Contributors
492
493
 
493
- Thanks to John Joseph Bachir, Eike Carls, Kai Chen, Erik Dahlstrand,
494
- Jeroen van Dijk, Sebastian Dyck, Jeremy Friesen, Daniel Kristensen, Brian Langenfeld,
495
- Georg Ledermann, Geoff Longman, Olly Lylo, Mark Mansour, Thomas Maurer,
496
- Mike Vincent
494
+ Thanks to John Joseph Bachir, Eike Carls, Kai Chen, Erik Dahlstrand, Jeroen van Dijk,
495
+ Alexander Dobriakov, Sebastian Dyck, Ari Epstein, Jeremy Friesen, Tim Harper, hollownest,
496
+ Daniel Kristensen, Brian Langenfeld, Georg Ledermann, Geoff Longman, Olly Lylo, Mark Mansour,
497
+ Thomas Maurer, TJ Singleton, Mike Vincent
497
498
 
498
499
 
499
500
  = Licence
data/Rakefile CHANGED
@@ -33,11 +33,3 @@ desc "clone the garlic repo (for running ci tasks)"
33
33
  task :get_garlic do
34
34
  sh "git clone git://github.com/ianwhite/garlic.git garlic"
35
35
  end
36
-
37
- desc "Expand filelist in src gemspec"
38
- task :build_gemspec do
39
- gemspec_data = File.read("declarative_authorization.gemspec.src")
40
- gemspec_data.gsub!(/\.files = (.*)/) {|m| ".files = #{eval($1).inspect}"}
41
- File.open("declarative_authorization.gemspec", "w") {|f| f.write(gemspec_data)}
42
- end
43
-
@@ -18,7 +18,7 @@ class AuthorizationRulesController < ApplicationController
18
18
  def index
19
19
  respond_to do |format|
20
20
  format.html do
21
- @auth_rules_script = File.read("#{RAILS_ROOT}/config/authorization_rules.rb")
21
+ @auth_rules_script = File.read("#{::Rails.root}/config/authorization_rules.rb")
22
22
  end
23
23
  end
24
24
  end
@@ -36,14 +36,14 @@ module AuthorizationRulesHelper
36
36
  note = %Q{<span class="note" title="#{h text}">[i]</span>}
37
37
  marked_up_by_line[line - 1] = note + marked_up_by_line[line - 1]
38
38
  end
39
- marked_up_by_line * "\n"
39
+ (marked_up_by_line * "\n").html_safe
40
40
  end
41
-
41
+
42
42
  def link_to_graph (title, options = {})
43
43
  type = options[:type] || ''
44
44
  link_to_function title, "$$('object')[0].data = '#{url_for :action => 'index', :format => 'svg', :type => type}'"
45
45
  end
46
-
46
+
47
47
  def navigation
48
48
  link_to("Rules", authorization_rules_path) << ' | ' <<
49
49
  link_to("Change Support", change_authorization_rules_path) << ' | ' <<
data/config/routes.rb CHANGED
@@ -1,6 +1,9 @@
1
- ActionController::Routing::Routes.draw do |map|
1
+ # Rails 3 depreciates ActionController::Routing::Routes
2
+ routes = (Rails.respond_to?(:application) ? Rails.application.routes : ActionController::Routing::Routes)
3
+
4
+ routes.draw do |map|
2
5
  if Authorization::activate_authorization_rules_browser?
3
- map.resources :authorization_rules, :only => [:index],
6
+ map.resources :authorization_rules, :only => [:index],
4
7
  :collection => {:graph => :get, :change => :get, :suggest_change => :get}
5
8
  map.resources :authorization_usages, :only => :index
6
9
  end
@@ -9,6 +9,8 @@ if Rails::VERSION::STRING < min_rails_version
9
9
  raise "declarative_authorization requires Rails #{min_rails_version}. You are using #{Rails::VERSION::STRING}."
10
10
  end
11
11
 
12
+ require File.join(%w{declarative_authorization railsengine}) if defined?(::Rails::Engine)
13
+
12
14
  ActionController::Base.send :include, Authorization::AuthorizationInController
13
15
  ActionController::Base.helper Authorization::AuthorizationHelper
14
16
 
@@ -20,7 +20,7 @@ module Authorization
20
20
  # The exception is raised to ensure that the entire rule is invalidated.
21
21
  class NilAttributeValueError < AuthorizationError; end
22
22
 
23
- AUTH_DSL_FILES = ["#{RAILS_ROOT}/config/authorization_rules.rb"] unless defined? AUTH_DSL_FILES
23
+ AUTH_DSL_FILES = [(Rails.root || Pathname.new('')).join("config", "authorization_rules.rb").to_s] unless defined? AUTH_DSL_FILES
24
24
 
25
25
  # Controller-independent method for retrieving the current user.
26
26
  # Needed for model security where the current controller is not available.
@@ -40,7 +40,7 @@ module Authorization
40
40
  end
41
41
 
42
42
  def self.activate_authorization_rules_browser? # :nodoc:
43
- ::RAILS_ENV == 'development'
43
+ ::Rails.env.development?
44
44
  end
45
45
 
46
46
  @@dot_path = "dot"
@@ -57,7 +57,7 @@ module Authorization
57
57
  # a certain privilege is granted for the current user.
58
58
  #
59
59
  class Engine
60
- attr_reader :roles, :role_titles, :role_descriptions, :privileges,
60
+ attr_reader :roles, :omnipotent_roles, :role_titles, :role_descriptions, :privileges,
61
61
  :privilege_hierarchy, :auth_rules, :role_hierarchy, :rev_priv_hierarchy,
62
62
  :rev_role_hierarchy
63
63
 
@@ -65,20 +65,14 @@ module Authorization
65
65
  # authorization configuration of +AUTH_DSL_FILES+. If given, may be either
66
66
  # a Reader object or a path to a configuration file.
67
67
  def initialize (reader = nil)
68
- if reader.nil?
69
- begin
70
- reader = Reader::DSLReader.load(AUTH_DSL_FILES)
71
- rescue SystemCallError
72
- reader = Reader::DSLReader.new
73
- end
74
- elsif reader.is_a?(String)
75
- reader = Reader::DSLReader.load(reader)
76
- end
68
+ reader = Reader::DSLReader.factory(reader || AUTH_DSL_FILES)
69
+
77
70
  @privileges = reader.privileges_reader.privileges
78
71
  # {priv => [[priv, ctx],...]}
79
72
  @privilege_hierarchy = reader.privileges_reader.privilege_hierarchy
80
73
  @auth_rules = reader.auth_rules_reader.auth_rules
81
74
  @roles = reader.auth_rules_reader.roles
75
+ @omnipotent_roles = reader.auth_rules_reader.omnipotent_roles
82
76
  @role_hierarchy = reader.auth_rules_reader.role_hierarchy
83
77
 
84
78
  @role_titles = reader.auth_rules_reader.role_titles
@@ -160,6 +154,8 @@ module Authorization
160
154
 
161
155
  user, roles, privileges = user_roles_privleges_from_options(privilege, options)
162
156
 
157
+ return true if roles.is_a?(Array) and not (roles & @omnipotent_roles).empty?
158
+
163
159
  # find a authorization rule that matches for at least one of the roles and
164
160
  # at least one of the given privileges
165
161
  attr_validator = AttributeValidator.new(self, user, options[:object], privilege, options[:context])
@@ -477,6 +473,14 @@ module Authorization
477
473
  "subclass of Enumerable as value, got: #{attr_value.inspect} " +
478
474
  "is_not_in #{evaluated.inspect}: #{e}"
479
475
  end
476
+ when :lt
477
+ attr_value && attr_value < evaluated
478
+ when :lte
479
+ attr_value && attr_value <= evaluated
480
+ when :gt
481
+ attr_value && attr_value > evaluated
482
+ when :gte
483
+ attr_value && attr_value >= evaluated
480
484
  else
481
485
  raise AuthorizationError, "Unknown operator #{value[0]}"
482
486
  end
@@ -521,8 +525,9 @@ module Authorization
521
525
  begin
522
526
  object.send(attr)
523
527
  rescue ArgumentError, NoMethodError => e
524
- raise AuthorizationUsageError, "Error when calling #{attr} on " +
525
- "#{object.inspect} for validating attribute: #{e}"
528
+ raise AuthorizationUsageError, "Error occurred while validating attribute ##{attr} on #{object.inspect}: #{e}.\n" +
529
+ "Please check your authorization rules and ensure the attribute is correctly spelled and \n" +
530
+ "corresponds to a method on the model you are authorizing for."
526
531
  end
527
532
  end
528
533
 
@@ -679,3 +684,4 @@ module Authorization
679
684
  end
680
685
  end
681
686
  end
687
+
@@ -112,7 +112,7 @@ module Authorization
112
112
  else
113
113
  !DEFAULT_DENY
114
114
  end
115
- rescue AuthorizationError => e
115
+ rescue NotAuthorized => e
116
116
  auth_exception = e
117
117
  end
118
118
 
@@ -274,10 +274,7 @@ module Authorization
274
274
  context = options[:context]
275
275
  actions = args.flatten
276
276
 
277
- # collect permits in controller array for use in one before_filter
278
- unless filter_chain.any? {|filter| filter.method == :filter_access_filter}
279
- before_filter :filter_access_filter
280
- end
277
+ before_filter :filter_access_filter
281
278
 
282
279
  filter_access_permissions.each do |perm|
283
280
  perm.remove_actions(actions)
@@ -69,11 +69,12 @@ module Authorization
69
69
  }.merge(options)
70
70
  engine = options[:engine] || Authorization::Engine.instance
71
71
 
72
- scope = ObligationScope.new( options[:model], {} )
72
+ obligation_scope = ObligationScope.new( options[:model], {} )
73
73
  engine.obligations( privileges, :user => options[:user], :context => options[:context] ).each do |obligation|
74
- scope.parse!( obligation )
74
+ obligation_scope.parse!( obligation )
75
75
  end
76
- scope
76
+
77
+ obligation_scope.scope
77
78
  end
78
79
 
79
80
  # Named scope for limiting query results according to the authorization
@@ -132,15 +133,17 @@ module Authorization
132
133
  :object => object, :context => options[:context])
133
134
  end
134
135
  end
135
-
136
- # after_find is only called if after_find is implemented
137
- after_find do |object|
138
- Authorization::Engine.instance.permit!(:read, :object => object,
139
- :context => options[:context])
140
- end
141
136
 
142
137
  if options[:include_read]
143
- def after_find; end
138
+ # after_find is only called if after_find is implemented
139
+ after_find do |object|
140
+ Authorization::Engine.instance.permit!(:read, :object => object,
141
+ :context => options[:context])
142
+ end
143
+
144
+ if Rails.version < "3"
145
+ def after_find; end
146
+ end
144
147
  end
145
148
 
146
149
  def self.using_access_control?
@@ -55,9 +55,9 @@ module Authorization
55
55
  def self.usages_by_controller
56
56
  # load each application controller
57
57
  begin
58
- Dir.foreach(File.join(RAILS_ROOT, %w{app controllers})) do |entry|
58
+ Dir.foreach(File.join(::Rails.root, %w{app controllers})) do |entry|
59
59
  if entry =~ /^\w+_controller\.rb$/
60
- require File.join(RAILS_ROOT, %w{app controllers}, entry)
60
+ require File.join(::Rails.root, %w{app controllers}, entry)
61
61
  end
62
62
  end
63
63
  rescue Errno::ENOENT
@@ -78,7 +78,7 @@ module Authorization
78
78
  end
79
79
  end
80
80
 
81
- actions = controller.public_instance_methods(false) - controller.hidden_actions
81
+ actions = controller.public_instance_methods(false) - controller.hidden_actions.to_a
82
82
  memo[controller] = actions.inject({}) do |actions_memo, action|
83
83
  action_sym = action.to_sym
84
84
  actions_memo[action_sym] =
@@ -171,7 +171,7 @@ module Authorization
171
171
 
172
172
  def request_with (user, method, xhr, action, params = {},
173
173
  session = {}, flash = {})
174
- session = session.merge({:user => user, :user_id => user.id})
174
+ session = session.merge({:user => user, :user_id => user && user.id})
175
175
  with_user(user) do
176
176
  if xhr
177
177
  xhr method, action, params, session, flash
@@ -33,6 +33,7 @@ module Authorization
33
33
  # [ :attr, :is, <user.id> ]
34
34
  # ]+
35
35
  #
36
+ # TODO update doc for Relations:
36
37
  # After successfully parsing an obligation, all of the stored paths and conditions are converted
37
38
  # into scope options (stored in +proxy_options+ as +:joins+ and +:conditions+). The resulting
38
39
  # scope may then be used to find all scoped objects for which at least one of the parsed
@@ -41,8 +42,25 @@ module Authorization
41
42
  # +@proxy_options[:joins] = { :bar => { :baz => :foo } }
42
43
  # @proxy_options[:conditions] = [ 'foos_bazzes.attr = :foos_bazzes__id_0', { :foos_bazzes__id_0 => 1 } ]+
43
44
  #
44
- class ObligationScope < ActiveRecord::NamedScope::Scope
45
-
45
+ class ObligationScope < (Rails.version < "3" ? ActiveRecord::NamedScope::Scope : ActiveRecord::Relation)
46
+ def initialize (model, options)
47
+ @finder_options = {}
48
+ if Rails.version < "3"
49
+ super(model, options)
50
+ else
51
+ super(model, model.table_name)
52
+ end
53
+ end
54
+
55
+ def scope
56
+ if Rails.version < "3"
57
+ self
58
+ else
59
+ # for Rails < 3: scope, after setting proxy_options
60
+ self.klass.scoped(@finder_options)
61
+ end
62
+ end
63
+
46
64
  # Consumes the given obligation, converting it into scope join and condition options.
47
65
  def parse!( obligation )
48
66
  @current_obligation = obligation
@@ -79,6 +97,18 @@ module Authorization
79
97
  raise "invalid obligation path #{[past_steps, steps].inspect}"
80
98
  end
81
99
  end
100
+
101
+ def top_level_model
102
+ if Rails.version < "3"
103
+ @proxy_scope
104
+ else
105
+ self.klass
106
+ end
107
+ end
108
+
109
+ def finder_options
110
+ Rails.version < "3" ? @proxy_options : @finder_options
111
+ end
82
112
 
83
113
  # At the end of every association path, we expect to see a comparison of some kind; for
84
114
  # example, +:attr => [ :is, :value ]+.
@@ -135,7 +165,7 @@ module Authorization
135
165
  def map_reflection_for( path )
136
166
  raise "reflection for #{path.inspect} already exists" unless reflections[path].nil?
137
167
 
138
- reflection = path.empty? ? @proxy_scope : begin
168
+ reflection = path.empty? ? top_level_model : begin
139
169
  parent = reflection_for( path[0..-2] )
140
170
  if !parent.respond_to?(:proxy_reflection) and parent.respond_to?(:klass)
141
171
  parent.klass.reflect_on_association( path.last )
@@ -151,7 +181,8 @@ module Authorization
151
181
  map_table_alias_for( path ) # Claim a table alias for the path.
152
182
 
153
183
  # Claim alias for join table
154
- if !reflection.respond_to?(:proxy_scope) and reflection.is_a?(ActiveRecord::Reflection::ThroughReflection)
184
+ # TODO change how this is checked
185
+ if !reflection.respond_to?(:proxy_reflection) and !reflection.respond_to?(:proxy_scope) and reflection.is_a?(ActiveRecord::Reflection::ThroughReflection)
155
186
  join_table_path = path[0..-2] + [reflection.options[:through]]
156
187
  reflection_for(join_table_path, true)
157
188
  end
@@ -209,7 +240,7 @@ module Authorization
209
240
  conditions.each do |path, expressions|
210
241
  model = model_for( path )
211
242
  table_alias = table_alias_for(path)
212
- parent_model = (path.length > 1 ? model_for(path[0..-2]) : @proxy_scope)
243
+ parent_model = (path.length > 1 ? model_for(path[0..-2]) : top_level_model)
213
244
  expressions.each do |expression|
214
245
  attribute, operator, value = expression
215
246
  # prevent unnecessary joins:
@@ -227,7 +258,8 @@ module Authorization
227
258
  end
228
259
  bindvar = "#{attribute_table_alias}__#{attribute_name}_#{obligation_index}".to_sym
229
260
 
230
- sql_attribute = "#{connection.quote_table_name(attribute_table_alias)}.#{connection.quote_table_name(attribute_name)}"
261
+ sql_attribute = "#{parent_model.connection.quote_table_name(attribute_table_alias)}." +
262
+ "#{parent_model.connection.quote_table_name(attribute_name)}"
231
263
  if value.nil? and [:is, :is_not].include?(operator)
232
264
  obligation_conds << "#{sql_attribute} IS #{[:contains, :is].include?(operator) ? '' : 'NOT '}NULL"
233
265
  else
@@ -236,6 +268,10 @@ module Authorization
236
268
  when :does_not_contain, :is_not then "<> :#{bindvar}"
237
269
  when :is_in, :intersects_with then "IN (:#{bindvar})"
238
270
  when :is_not_in then "NOT IN (:#{bindvar})"
271
+ when :lt then "< :#{bindvar}"
272
+ when :lte then "<= :#{bindvar}"
273
+ when :gt then "> :#{bindvar}"
274
+ when :gte then ">= :#{bindvar}"
239
275
  else raise AuthorizationUsageError, "Unknown operator: #{operator}"
240
276
  end
241
277
  obligation_conds << "#{sql_attribute} #{attribute_operator}"
@@ -247,7 +283,8 @@ module Authorization
247
283
  conds << "(#{obligation_conds.join(' AND ')})"
248
284
  end
249
285
  (delete_paths - used_paths).each {|path| reflections.delete(path)}
250
- @proxy_options[:conditions] = [ conds.join( " OR " ), binds ]
286
+
287
+ finder_options[:conditions] = [ conds.join( " OR " ), binds ]
251
288
  end
252
289
 
253
290
  def attribute_value (value)
@@ -259,7 +296,7 @@ module Authorization
259
296
  # Parses all of the defined obligation joins and defines the scope's :joins or :includes option.
260
297
  # TODO: Support non-linear association paths. Right now, we just break down the longest path parsed.
261
298
  def rebuild_join_options!
262
- joins = (@proxy_options[:joins] || []) + (@proxy_options[:includes] || [])
299
+ joins = (finder_options[:joins] || []) + (finder_options[:includes] || [])
263
300
 
264
301
  reflections.keys.each do |path|
265
302
  next if path.empty? or @join_table_joins.include?(path)
@@ -283,11 +320,11 @@ module Authorization
283
320
  when 0 then
284
321
  # No obligation conditions means we don't have to mess with joins or includes at all.
285
322
  when 1 then
286
- @proxy_options[:joins] = joins
287
- @proxy_options.delete( :include )
323
+ finder_options[:joins] = joins
324
+ finder_options.delete( :include )
288
325
  else
289
- @proxy_options.delete( :joins )
290
- @proxy_options[:include] = joins
326
+ finder_options.delete( :joins )
327
+ finder_options[:include] = joins
291
328
  end
292
329
  end
293
330
 
@@ -313,4 +350,5 @@ module Authorization
313
350
  end
314
351
  end
315
352
  end
316
- end
353
+ end
354
+
@@ -11,4 +11,12 @@ class Hash
11
11
  end
12
12
  end
13
13
  end
14
- end
14
+ end
15
+
16
+ class String
17
+ unless "".respond_to?(:html_safe)
18
+ def html_safe
19
+ self
20
+ end
21
+ end
22
+ end