declarative_authorization 0.4.1 → 0.5

Sign up to get free protection for your applications and to get access to all the features.
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