zeiv-declarative_authorization 1.0.0.pre
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.
- checksums.yaml +7 -0
- data/CHANGELOG +189 -0
- data/MIT-LICENSE +20 -0
- data/README.rdoc +632 -0
- data/Rakefile +53 -0
- data/app/controllers/authorization_rules_controller.rb +258 -0
- data/app/controllers/authorization_usages_controller.rb +22 -0
- data/app/helpers/authorization_rules_helper.rb +218 -0
- data/app/views/authorization_rules/_change.erb +58 -0
- data/app/views/authorization_rules/_show_graph.erb +44 -0
- data/app/views/authorization_rules/_suggestions.erb +48 -0
- data/app/views/authorization_rules/change.html.erb +169 -0
- data/app/views/authorization_rules/graph.dot.erb +68 -0
- data/app/views/authorization_rules/graph.html.erb +47 -0
- data/app/views/authorization_rules/index.html.erb +17 -0
- data/app/views/authorization_usages/index.html.erb +36 -0
- data/authorization_rules.dist.rb +20 -0
- data/config/routes.rb +20 -0
- data/garlic_example.rb +20 -0
- data/init.rb +5 -0
- data/lib/declarative_authorization.rb +19 -0
- data/lib/declarative_authorization/adapters/active_record.rb +13 -0
- data/lib/declarative_authorization/adapters/active_record/base_extensions.rb +0 -0
- data/lib/declarative_authorization/adapters/active_record/obligation_scope_builder.rb +0 -0
- data/lib/declarative_authorization/authorization.rb +798 -0
- data/lib/declarative_authorization/development_support/analyzer.rb +261 -0
- data/lib/declarative_authorization/development_support/change_analyzer.rb +253 -0
- data/lib/declarative_authorization/development_support/change_supporter.rb +620 -0
- data/lib/declarative_authorization/development_support/development_support.rb +243 -0
- data/lib/declarative_authorization/helper.rb +68 -0
- data/lib/declarative_authorization/in_controller.rb +703 -0
- data/lib/declarative_authorization/in_model.rb +188 -0
- data/lib/declarative_authorization/maintenance.rb +210 -0
- data/lib/declarative_authorization/obligation_scope.rb +361 -0
- data/lib/declarative_authorization/rails_legacy.rb +22 -0
- data/lib/declarative_authorization/railsengine.rb +6 -0
- data/lib/declarative_authorization/reader.rb +546 -0
- data/lib/generators/authorization/install/install_generator.rb +77 -0
- data/lib/generators/authorization/rules/rules_generator.rb +14 -0
- data/lib/generators/authorization/rules/templates/authorization_rules.rb +27 -0
- data/lib/tasks/authorization_tasks.rake +89 -0
- data/test/authorization_test.rb +1124 -0
- data/test/controller_filter_resource_access_test.rb +575 -0
- data/test/controller_test.rb +480 -0
- data/test/database.yml +3 -0
- data/test/dsl_reader_test.rb +178 -0
- data/test/helper_test.rb +247 -0
- data/test/maintenance_test.rb +46 -0
- data/test/model_test.rb +2008 -0
- data/test/schema.sql +56 -0
- data/test/test_helper.rb +255 -0
- metadata +95 -0
@@ -0,0 +1,36 @@
|
|
1
|
+
<h1>Authorization Usage</h1>
|
2
|
+
<%= render 'authorization_rules/show_graph' %>
|
3
|
+
<p>Filter rules in actions by controller:</p>
|
4
|
+
<p><%= navigation %></p>
|
5
|
+
<style type="text/css">
|
6
|
+
.auth-usages th { text-align: left; padding-top: 1em }
|
7
|
+
.auth-usages td { padding-right: 1em }
|
8
|
+
.auth-usages tr.action { cursor: pointer }
|
9
|
+
.auth-usages tr.unprotected { background: #FFA399 }
|
10
|
+
.auth-usages tr.no-attribute-check { background: #FFE599 }
|
11
|
+
/*.auth-usages tr.catch-all td.privilege,*/
|
12
|
+
.auth-usages tr.default-privilege td.privilege,
|
13
|
+
.auth-usages tr.default-context td.context { color: #888888 }
|
14
|
+
#graph-container {margin: 1em; border:1px solid #ccc; max-width:50%; position:fixed; right:0;}
|
15
|
+
</style>
|
16
|
+
<table class="auth-usages">
|
17
|
+
<% @auth_usages_by_controller.keys.sort {|c1, c2| c1.name <=> c2.name}.each do |controller| %>
|
18
|
+
<% default_context = controller.controller_name.to_sym rescue nil %>
|
19
|
+
<tr>
|
20
|
+
<th colspan="3"><%= h controller.name.underscore.sub(/_controller\Z/, '') %></th>
|
21
|
+
</tr>
|
22
|
+
<% @auth_usages_by_controller[controller].keys.sort {|c1, c2| c1.to_s <=> c2.to_s}.each do |action| %>
|
23
|
+
<% auth_info = @auth_usages_by_controller[controller][action] %>
|
24
|
+
<% first_permission = auth_info[:controller_permissions] && auth_info[:controller_permissions][0] %>
|
25
|
+
<tr class="action <%= auth_usage_info_classes(auth_info) %>" title="<%= auth_usage_info_title(auth_info) %>" onclick="show_graph('<%= auth_info[:privilege] || action %>','<%= auth_info[:context] || default_context %>')">
|
26
|
+
<td><%= h action %></td>
|
27
|
+
<% if first_permission %>
|
28
|
+
<td class="privilege"><%= h auth_info[:privilege] || action %></td>
|
29
|
+
<td class="context"><%= h auth_info[:context] || default_context %></td>
|
30
|
+
<% else %>
|
31
|
+
<td></td><td></td>
|
32
|
+
<% end %>
|
33
|
+
</tr>
|
34
|
+
<% end %>
|
35
|
+
<% end %>
|
36
|
+
</table>
|
@@ -0,0 +1,20 @@
|
|
1
|
+
authorization do
|
2
|
+
role :guest do
|
3
|
+
# add permissions for guests here, e.g.
|
4
|
+
#has_permission_on :conferences, :to => :read
|
5
|
+
end
|
6
|
+
|
7
|
+
# permissions on other roles, such as
|
8
|
+
#role :admin do
|
9
|
+
# has_permission_on :conferences, :to => :manage
|
10
|
+
#end
|
11
|
+
end
|
12
|
+
|
13
|
+
privileges do
|
14
|
+
# default privilege hierarchies to facilitate RESTful Rails apps
|
15
|
+
privilege :manage, :includes => [:create, :read, :update, :delete]
|
16
|
+
privilege :read, :includes => [:index, :show]
|
17
|
+
privilege :create, :includes => :new
|
18
|
+
privilege :update, :includes => :edit
|
19
|
+
privilege :delete, :includes => :destroy
|
20
|
+
end
|
data/config/routes.rb
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
if Authorization::activate_authorization_rules_browser?
|
2
|
+
if Rails.respond_to?(:application)
|
3
|
+
Rails.application.routes.draw do
|
4
|
+
resources :authorization_rules, :only => [:index] do
|
5
|
+
collection do
|
6
|
+
get :graph
|
7
|
+
get :change
|
8
|
+
get :suggest_change
|
9
|
+
end
|
10
|
+
end
|
11
|
+
resources :authorization_usages, :only => :index
|
12
|
+
end
|
13
|
+
else
|
14
|
+
ActionController::Routing::Routes.draw do |map|
|
15
|
+
map.resources :authorization_rules, :only => [:index],
|
16
|
+
:collection => {:graph => :get, :change => :get, :suggest_change => :get}
|
17
|
+
map.resources :authorization_usages, :only => :index
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
data/garlic_example.rb
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
garlic do
|
2
|
+
repo 'rails', :url => 'git://github.com/rails/rails'#, :local => "~/dev/vendor/rails"
|
3
|
+
repo 'declarative_authorization', :path => '.'
|
4
|
+
|
5
|
+
target 'edge'
|
6
|
+
target '2.1-stable', :branch => 'origin/2-1-stable'
|
7
|
+
target '2.2.0-RC1', :tag => 'v2.2.0'
|
8
|
+
|
9
|
+
all_targets do
|
10
|
+
prepare do
|
11
|
+
plugin 'declarative_authorization', :clone => true
|
12
|
+
end
|
13
|
+
|
14
|
+
run do
|
15
|
+
cd "vendor/plugins/declarative_authorization" do
|
16
|
+
sh "rake"
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
data/init.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
require File.join(%w{declarative_authorization rails_legacy})
|
2
|
+
require File.join(%w{declarative_authorization helper})
|
3
|
+
require File.join(%w{declarative_authorization in_controller})
|
4
|
+
if defined?(ActiveRecord)
|
5
|
+
require File.join(%w{declarative_authorization in_model})
|
6
|
+
require File.join(%w{declarative_authorization obligation_scope})
|
7
|
+
end
|
8
|
+
|
9
|
+
min_rails_version = "2.1.0"
|
10
|
+
if Rails::VERSION::STRING < min_rails_version
|
11
|
+
raise "declarative_authorization requires Rails #{min_rails_version}. You are using #{Rails::VERSION::STRING}."
|
12
|
+
end
|
13
|
+
|
14
|
+
require File.join(%w{declarative_authorization railsengine}) if defined?(::Rails::Engine)
|
15
|
+
|
16
|
+
ActionController::Base.send :include, Authorization::AuthorizationInController
|
17
|
+
ActionController::Base.helper Authorization::AuthorizationHelper
|
18
|
+
|
19
|
+
ActiveRecord::Base.send :include, Authorization::AuthorizationInModel if defined?(ActiveRecord)
|
@@ -0,0 +1,13 @@
|
|
1
|
+
case ActiveRecord::VERSION::MAJOR
|
2
|
+
when 3, 4
|
3
|
+
# ActiveRecord::Relation.send :include, Squeel::Nodes::Aliasing
|
4
|
+
# require 'squeel/adapters/active_record/join_dependency_extensions'
|
5
|
+
# require 'squeel/adapters/active_record/base_extensions'
|
6
|
+
|
7
|
+
adapter_directory = "#{ActiveRecord::VERSION::MAJOR}.#{ActiveRecord::VERSION::MINOR}"
|
8
|
+
Dir[File.expand_path("../active_record/#{adapter_directory}/*.rb", __FILE__)].each do |f|
|
9
|
+
require f
|
10
|
+
end
|
11
|
+
else
|
12
|
+
raise NotImplementedError, "DeclarativeAuthorization does not support Active Record version #{ActiveRecord::VERSION::STRING}"
|
13
|
+
end
|
File without changes
|
File without changes
|
@@ -0,0 +1,798 @@
|
|
1
|
+
# Authorization
|
2
|
+
require File.dirname(__FILE__) + '/reader.rb'
|
3
|
+
require "set"
|
4
|
+
require "forwardable"
|
5
|
+
|
6
|
+
module Authorization
|
7
|
+
# An exception raised if anything goes wrong in the Authorization realm
|
8
|
+
class AuthorizationError < StandardError ; end
|
9
|
+
# NotAuthorized is raised if the current user is not allowed to perform
|
10
|
+
# the given operation possibly on a specific object.
|
11
|
+
class NotAuthorized < AuthorizationError ; end
|
12
|
+
# AttributeAuthorizationError is more specific than NotAuthorized, signaling
|
13
|
+
# that the access was denied on the grounds of attribute conditions.
|
14
|
+
class AttributeAuthorizationError < NotAuthorized ; end
|
15
|
+
# AuthorizationUsageError is used whenever a situation is encountered
|
16
|
+
# in which the application misused the plugin. That is, if, e.g.,
|
17
|
+
# authorization rules may not be evaluated.
|
18
|
+
class AuthorizationUsageError < AuthorizationError ; end
|
19
|
+
# NilAttributeValueError is raised by Attribute#validate? when it hits a nil attribute value.
|
20
|
+
# The exception is raised to ensure that the entire rule is invalidated.
|
21
|
+
class NilAttributeValueError < AuthorizationError; end
|
22
|
+
|
23
|
+
AUTH_DSL_FILES = [Pathname.new(Rails.root || '').join("config", "authorization_rules.rb").to_s] unless defined? AUTH_DSL_FILES
|
24
|
+
|
25
|
+
# Controller-independent method for retrieving the current user.
|
26
|
+
# Needed for model security where the current controller is not available.
|
27
|
+
def self.current_user
|
28
|
+
Thread.current["current_user"] || AnonymousUser.new
|
29
|
+
end
|
30
|
+
|
31
|
+
# Controller-independent method for setting the current user.
|
32
|
+
def self.current_user=(user)
|
33
|
+
Thread.current["current_user"] = user
|
34
|
+
end
|
35
|
+
|
36
|
+
# For use in test cases only
|
37
|
+
def self.ignore_access_control (state = nil) # :nodoc:
|
38
|
+
Thread.current["ignore_access_control"] = state unless state.nil?
|
39
|
+
Thread.current["ignore_access_control"] || false
|
40
|
+
end
|
41
|
+
|
42
|
+
def self.activate_authorization_rules_browser? # :nodoc:
|
43
|
+
::Rails.env.development?
|
44
|
+
end
|
45
|
+
|
46
|
+
@@dot_path = "dot"
|
47
|
+
def self.dot_path
|
48
|
+
@@dot_path
|
49
|
+
end
|
50
|
+
|
51
|
+
def self.dot_path= (path)
|
52
|
+
@@dot_path = path
|
53
|
+
end
|
54
|
+
|
55
|
+
@@default_role = :guest
|
56
|
+
def self.default_role
|
57
|
+
@@default_role
|
58
|
+
end
|
59
|
+
|
60
|
+
def self.default_role= (role)
|
61
|
+
@@default_role = role.to_sym
|
62
|
+
end
|
63
|
+
|
64
|
+
def self.is_a_association_proxy? (object)
|
65
|
+
if Rails.version < "3.2"
|
66
|
+
object.respond_to?(:proxy_reflection)
|
67
|
+
else
|
68
|
+
object.respond_to?(:proxy_association)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
# Authorization::Engine implements the reference monitor. It may be used
|
73
|
+
# for querying the permission and retrieving obligations under which
|
74
|
+
# a certain privilege is granted for the current user.
|
75
|
+
#
|
76
|
+
class Engine
|
77
|
+
extend Forwardable
|
78
|
+
attr_reader :reader
|
79
|
+
|
80
|
+
def_delegators :@reader, :auth_rules_reader, :privileges_reader, :load, :load!
|
81
|
+
def_delegators :auth_rules_reader, :auth_rules, :roles, :omnipotent_roles, :role_hierarchy, :role_titles, :role_descriptions
|
82
|
+
def_delegators :privileges_reader, :privileges, :privilege_hierarchy
|
83
|
+
|
84
|
+
# If +reader+ is not given, a new one is created with the default
|
85
|
+
# authorization configuration of +AUTH_DSL_FILES+. If given, may be either
|
86
|
+
# a Reader object or a path to a configuration file.
|
87
|
+
def initialize (reader = nil)
|
88
|
+
#@auth_rules = AuthorizationRuleSet.new reader.auth_rules_reader.auth_rules
|
89
|
+
@reader = Reader::DSLReader.factory(reader || AUTH_DSL_FILES)
|
90
|
+
end
|
91
|
+
|
92
|
+
def initialize_copy (from) # :nodoc:
|
93
|
+
@reader = from.reader.clone
|
94
|
+
end
|
95
|
+
|
96
|
+
# {[priv, ctx] => [priv, ...]}
|
97
|
+
def rev_priv_hierarchy
|
98
|
+
if @rev_priv_hierarchy.nil?
|
99
|
+
@rev_priv_hierarchy = {}
|
100
|
+
privilege_hierarchy.each do |key, value|
|
101
|
+
value.each do |val|
|
102
|
+
@rev_priv_hierarchy[val] ||= []
|
103
|
+
@rev_priv_hierarchy[val] << key
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
@rev_priv_hierarchy
|
108
|
+
end
|
109
|
+
|
110
|
+
# {[priv, ctx] => [priv, ...]}
|
111
|
+
def rev_role_hierarchy
|
112
|
+
if @rev_role_hierarchy.nil?
|
113
|
+
@rev_role_hierarchy = {}
|
114
|
+
role_hierarchy.each do |higher_role, lower_roles|
|
115
|
+
lower_roles.each do |role|
|
116
|
+
(@rev_role_hierarchy[role] ||= []) << higher_role
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
@rev_role_hierarchy
|
121
|
+
end
|
122
|
+
|
123
|
+
# Returns true if privilege is met by the current user. Raises
|
124
|
+
# AuthorizationError otherwise. +privilege+ may be given with or
|
125
|
+
# without context. In the latter case, the :+context+ option is
|
126
|
+
# required.
|
127
|
+
#
|
128
|
+
# Options:
|
129
|
+
# [:+context+]
|
130
|
+
# The context part of the privilege.
|
131
|
+
# Defaults either to the tableized +class_name+ of the given :+object+, if given.
|
132
|
+
# That is, :+users+ for :+object+ of type User.
|
133
|
+
# Raises AuthorizationUsageError if context is missing and not to be inferred.
|
134
|
+
# [:+object+] An context object to test attribute checks against.
|
135
|
+
# [:+skip_attribute_test+]
|
136
|
+
# Skips those attribute checks in the
|
137
|
+
# authorization rules. Defaults to false.
|
138
|
+
# [:+user+]
|
139
|
+
# The user to check the authorization for.
|
140
|
+
# Defaults to Authorization#current_user.
|
141
|
+
# [:+bang+]
|
142
|
+
# Should NotAuthorized exceptions be raised
|
143
|
+
# Defaults to true.
|
144
|
+
#
|
145
|
+
def permit! (privilege, options = {})
|
146
|
+
return true if Authorization.ignore_access_control
|
147
|
+
options = {
|
148
|
+
:object => nil,
|
149
|
+
:skip_attribute_test => false,
|
150
|
+
:context => nil,
|
151
|
+
:bang => true
|
152
|
+
}.merge(options)
|
153
|
+
|
154
|
+
# Make sure we're handling all privileges as symbols.
|
155
|
+
privilege = privilege.is_a?( Array ) ?
|
156
|
+
privilege.flatten.collect { |priv| priv.to_sym } :
|
157
|
+
privilege.to_sym
|
158
|
+
|
159
|
+
#
|
160
|
+
# If the object responds to :proxy_reflection, we're probably working with
|
161
|
+
# an association proxy. Use 'new' to leverage ActiveRecord's builder
|
162
|
+
# functionality to obtain an object against which we can check permissions.
|
163
|
+
#
|
164
|
+
# Example: permit!( :edit, :object => user.posts )
|
165
|
+
#
|
166
|
+
if Authorization.is_a_association_proxy?(options[:object]) && options[:object].respond_to?(:new)
|
167
|
+
options[:object] = (Rails.version < "3.0" ? options[:object] : options[:object].where(nil)).new
|
168
|
+
end
|
169
|
+
|
170
|
+
options[:context] ||= options[:object] && (
|
171
|
+
options[:object].class.respond_to?(:decl_auth_context) ?
|
172
|
+
options[:object].class.decl_auth_context :
|
173
|
+
options[:object].class.name.tableize.to_sym
|
174
|
+
) rescue NoMethodError
|
175
|
+
|
176
|
+
user, roles, privileges = user_roles_privleges_from_options(privilege, options)
|
177
|
+
|
178
|
+
return true if roles.is_a?(Array) and not (roles & omnipotent_roles).empty?
|
179
|
+
|
180
|
+
# find a authorization rule that matches for at least one of the roles and
|
181
|
+
# at least one of the given privileges
|
182
|
+
attr_validator = AttributeValidator.new(self, user, options[:object], privilege, options[:context])
|
183
|
+
rules = matching_auth_rules(roles, privileges, options[:context])
|
184
|
+
|
185
|
+
# Test each rule in turn to see whether any one of them is satisfied.
|
186
|
+
rules.each do |rule|
|
187
|
+
return true if rule.validate?(attr_validator, options[:skip_attribute_test])
|
188
|
+
end
|
189
|
+
|
190
|
+
if options[:bang]
|
191
|
+
if rules.empty?
|
192
|
+
raise NotAuthorized, "No matching rules found for #{privilege} for #{user.inspect} " +
|
193
|
+
"(roles #{roles.inspect}, privileges #{privileges.inspect}, " +
|
194
|
+
"context #{options[:context].inspect})."
|
195
|
+
else
|
196
|
+
raise AttributeAuthorizationError, "#{privilege} not allowed for #{user.inspect} on #{(options[:object] || options[:context]).inspect}."
|
197
|
+
end
|
198
|
+
else
|
199
|
+
false
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
203
|
+
# Calls permit! but doesn't raise authorization errors. If no exception is
|
204
|
+
# raised, permit? returns true and yields to the optional block.
|
205
|
+
def permit? (privilege, options = {}) # :yields:
|
206
|
+
if permit!(privilege, options.merge(:bang=> false))
|
207
|
+
yield if block_given?
|
208
|
+
true
|
209
|
+
else
|
210
|
+
false
|
211
|
+
end
|
212
|
+
end
|
213
|
+
|
214
|
+
# Returns the obligations to be met by the current user for the given
|
215
|
+
# privilege as an array of obligation hashes in form of
|
216
|
+
# [{:object_attribute => obligation_value, ...}, ...]
|
217
|
+
# where +obligation_value+ is either (recursively) another obligation hash
|
218
|
+
# or a value spec, such as
|
219
|
+
# [operator, literal_value]
|
220
|
+
# The obligation hashes in the array should be OR'ed, conditions inside
|
221
|
+
# the hashes AND'ed.
|
222
|
+
#
|
223
|
+
# Example
|
224
|
+
# {:branch => {:company => [:is, 24]}, :active => [:is, true]}
|
225
|
+
#
|
226
|
+
# Options
|
227
|
+
# [:+context+] See permit!
|
228
|
+
# [:+user+] See permit!
|
229
|
+
#
|
230
|
+
def obligations (privilege, options = {})
|
231
|
+
options = {:context => nil}.merge(options)
|
232
|
+
user, roles, privileges = user_roles_privleges_from_options(privilege, options)
|
233
|
+
|
234
|
+
permit!(privilege, :skip_attribute_test => true, :user => user, :context => options[:context])
|
235
|
+
|
236
|
+
return [] if roles.is_a?(Array) and not (roles & omnipotent_roles).empty?
|
237
|
+
|
238
|
+
attr_validator = AttributeValidator.new(self, user, nil, privilege, options[:context])
|
239
|
+
matching_auth_rules(roles, privileges, options[:context]).collect do |rule|
|
240
|
+
rule.obligations(attr_validator)
|
241
|
+
end.flatten
|
242
|
+
end
|
243
|
+
|
244
|
+
# Returns the description for the given role. The description may be
|
245
|
+
# specified with the authorization rules. Returns +nil+ if none was
|
246
|
+
# given.
|
247
|
+
def description_for (role)
|
248
|
+
role_descriptions[role]
|
249
|
+
end
|
250
|
+
|
251
|
+
# Returns the title for the given role. The title may be
|
252
|
+
# specified with the authorization rules. Returns +nil+ if none was
|
253
|
+
# given.
|
254
|
+
def title_for (role)
|
255
|
+
role_titles[role]
|
256
|
+
end
|
257
|
+
|
258
|
+
# Returns the role symbols of the given user.
|
259
|
+
def roles_for (user)
|
260
|
+
user ||= Authorization.current_user
|
261
|
+
raise AuthorizationUsageError, "User object doesn't respond to roles (#{user.inspect})" \
|
262
|
+
if !user.respond_to?(:role_symbols) and !user.respond_to?(:roles)
|
263
|
+
|
264
|
+
Rails.logger.info("The use of user.roles is deprecated. Please add a method " +
|
265
|
+
"role_symbols to your User model.") if defined?(Rails) and Rails.respond_to?(:logger) and !user.respond_to?(:role_symbols)
|
266
|
+
|
267
|
+
roles = user.respond_to?(:role_symbols) ? user.role_symbols : user.roles
|
268
|
+
|
269
|
+
raise AuthorizationUsageError, "User.#{user.respond_to?(:role_symbols) ? 'role_symbols' : 'roles'} " +
|
270
|
+
"doesn't return an Array of Symbols (#{roles.inspect})" \
|
271
|
+
if !roles.is_a?(Array) or (!roles.empty? and !roles[0].is_a?(Symbol))
|
272
|
+
|
273
|
+
(roles.empty? ? [Authorization.default_role] : roles)
|
274
|
+
end
|
275
|
+
|
276
|
+
# Returns the role symbols and inherritted role symbols for the given user
|
277
|
+
def roles_with_hierarchy_for(user)
|
278
|
+
flatten_roles(roles_for(user))
|
279
|
+
end
|
280
|
+
|
281
|
+
def self.development_reload?
|
282
|
+
if Rails.env.development?
|
283
|
+
mod_time = AUTH_DSL_FILES.map { |m| File.mtime(m) rescue Time.at(0) }.flatten.max
|
284
|
+
@@auth_dsl_last_modified ||= mod_time
|
285
|
+
if mod_time > @@auth_dsl_last_modified
|
286
|
+
@@auth_dsl_last_modified = mod_time
|
287
|
+
return true
|
288
|
+
end
|
289
|
+
end
|
290
|
+
end
|
291
|
+
|
292
|
+
# Returns an instance of Engine, which is created if there isn't one
|
293
|
+
# yet. If +dsl_file+ is given, it is passed on to Engine.new and
|
294
|
+
# a new instance is always created.
|
295
|
+
def self.instance (dsl_file = nil)
|
296
|
+
if dsl_file or development_reload?
|
297
|
+
@@instance = new(dsl_file)
|
298
|
+
else
|
299
|
+
@@instance ||= new
|
300
|
+
end
|
301
|
+
end
|
302
|
+
|
303
|
+
class AttributeValidator # :nodoc:
|
304
|
+
attr_reader :user, :object, :engine, :context, :privilege
|
305
|
+
def initialize (engine, user, object = nil, privilege = nil, context = nil)
|
306
|
+
@engine = engine
|
307
|
+
@user = user
|
308
|
+
@object = object
|
309
|
+
@privilege = privilege
|
310
|
+
@context = context
|
311
|
+
end
|
312
|
+
|
313
|
+
def evaluate (value_block)
|
314
|
+
# TODO cache?
|
315
|
+
instance_eval(&value_block)
|
316
|
+
end
|
317
|
+
end
|
318
|
+
|
319
|
+
private
|
320
|
+
def user_roles_privleges_from_options(privilege, options)
|
321
|
+
options = {
|
322
|
+
:user => nil,
|
323
|
+
:context => nil,
|
324
|
+
:user_roles => nil
|
325
|
+
}.merge(options)
|
326
|
+
user = options[:user] || Authorization.current_user
|
327
|
+
privileges = privilege.is_a?(Array) ? privilege : [privilege]
|
328
|
+
|
329
|
+
raise AuthorizationUsageError, "No user object given (#{user.inspect}) or " +
|
330
|
+
"set through Authorization.current_user" unless user
|
331
|
+
|
332
|
+
roles = options[:user_roles] || flatten_roles(roles_for(user))
|
333
|
+
privileges = flatten_privileges privileges, options[:context]
|
334
|
+
[user, roles, privileges]
|
335
|
+
end
|
336
|
+
|
337
|
+
def flatten_roles (roles, flattened_roles = Set.new)
|
338
|
+
# TODO caching?
|
339
|
+
roles.reject {|role| flattened_roles.include?(role)}.each do |role|
|
340
|
+
flattened_roles << role
|
341
|
+
flatten_roles(role_hierarchy[role], flattened_roles) if role_hierarchy[role]
|
342
|
+
end
|
343
|
+
flattened_roles.to_a
|
344
|
+
end
|
345
|
+
|
346
|
+
# Returns the privilege hierarchy flattened for given privileges in context.
|
347
|
+
def flatten_privileges (privileges, context = nil, flattened_privileges = Set.new)
|
348
|
+
# TODO caching?
|
349
|
+
raise AuthorizationUsageError, "No context given or inferable from object" unless context
|
350
|
+
privileges.reject {|priv| flattened_privileges.include?(priv)}.each do |priv|
|
351
|
+
flattened_privileges << priv
|
352
|
+
flatten_privileges(rev_priv_hierarchy[[priv, nil]], context, flattened_privileges) if rev_priv_hierarchy[[priv, nil]]
|
353
|
+
flatten_privileges(rev_priv_hierarchy[[priv, context]], context, flattened_privileges) if rev_priv_hierarchy[[priv, context]]
|
354
|
+
end
|
355
|
+
flattened_privileges.to_a
|
356
|
+
end
|
357
|
+
|
358
|
+
def matching_auth_rules (roles, privileges, context)
|
359
|
+
auth_rules.matching(roles, privileges, context)
|
360
|
+
end
|
361
|
+
end
|
362
|
+
|
363
|
+
|
364
|
+
class AuthorizationRuleSet
|
365
|
+
include Enumerable
|
366
|
+
extend Forwardable
|
367
|
+
def_delegators :@rules, :each, :length, :[]
|
368
|
+
|
369
|
+
def initialize (rules = [])
|
370
|
+
@rules = rules.clone
|
371
|
+
reset!
|
372
|
+
end
|
373
|
+
|
374
|
+
def initialize_copy (source)
|
375
|
+
@rules = @rules.collect {|rule| rule.clone}
|
376
|
+
reset!
|
377
|
+
end
|
378
|
+
|
379
|
+
def matching(roles, privileges, context)
|
380
|
+
roles = [roles] unless roles.is_a?(Array)
|
381
|
+
rules = cached_auth_rules[context] || []
|
382
|
+
rules.select do |rule|
|
383
|
+
rule.matches? roles, privileges, context
|
384
|
+
end
|
385
|
+
end
|
386
|
+
def delete rule
|
387
|
+
@rules.delete rule
|
388
|
+
reset!
|
389
|
+
end
|
390
|
+
def << rule
|
391
|
+
@rules << rule
|
392
|
+
reset!
|
393
|
+
end
|
394
|
+
def each &block
|
395
|
+
@rules.each &block
|
396
|
+
end
|
397
|
+
|
398
|
+
private
|
399
|
+
def reset!
|
400
|
+
@cached_auth_rules =nil
|
401
|
+
end
|
402
|
+
def cached_auth_rules
|
403
|
+
return @cached_auth_rules if @cached_auth_rules
|
404
|
+
@cached_auth_rules = {}
|
405
|
+
@rules.each do |rule|
|
406
|
+
rule.contexts.each do |context|
|
407
|
+
@cached_auth_rules[context] ||= []
|
408
|
+
@cached_auth_rules[context] << rule
|
409
|
+
end
|
410
|
+
end
|
411
|
+
@cached_auth_rules
|
412
|
+
end
|
413
|
+
end
|
414
|
+
class AuthorizationRule
|
415
|
+
attr_reader :attributes, :contexts, :role, :privileges, :join_operator,
|
416
|
+
:source_file, :source_line
|
417
|
+
|
418
|
+
def initialize (role, privileges = [], contexts = nil, join_operator = :or,
|
419
|
+
options = {})
|
420
|
+
@role = role
|
421
|
+
@privileges = Set.new(privileges)
|
422
|
+
@contexts = Set.new((contexts && !contexts.is_a?(Array) ? [contexts] : contexts))
|
423
|
+
@join_operator = join_operator
|
424
|
+
@attributes = []
|
425
|
+
@source_file = options[:source_file]
|
426
|
+
@source_line = options[:source_line]
|
427
|
+
end
|
428
|
+
|
429
|
+
def initialize_copy (from)
|
430
|
+
@privileges = @privileges.clone
|
431
|
+
@contexts = @contexts.clone
|
432
|
+
@attributes = @attributes.collect {|attribute| attribute.clone }
|
433
|
+
end
|
434
|
+
|
435
|
+
def append_privileges (privs)
|
436
|
+
@privileges.merge(privs)
|
437
|
+
end
|
438
|
+
|
439
|
+
def append_attribute (attribute)
|
440
|
+
@attributes << attribute
|
441
|
+
end
|
442
|
+
|
443
|
+
def matches? (roles, privs, context = nil)
|
444
|
+
roles = [roles] unless roles.is_a?(Array)
|
445
|
+
@contexts.include?(context) and roles.include?(@role) and
|
446
|
+
not (@privileges & privs).empty?
|
447
|
+
end
|
448
|
+
|
449
|
+
def validate? (attr_validator, skip_attribute = false)
|
450
|
+
skip_attribute or @attributes.empty? or
|
451
|
+
@attributes.send(@join_operator == :and ? :all? : :any?) do |attr|
|
452
|
+
begin
|
453
|
+
attr.validate?(attr_validator)
|
454
|
+
rescue NilAttributeValueError => e
|
455
|
+
nil # Bumping up against a nil attribute value flunks the rule.
|
456
|
+
end
|
457
|
+
end
|
458
|
+
end
|
459
|
+
|
460
|
+
def obligations (attr_validator)
|
461
|
+
exceptions = []
|
462
|
+
obligations = @attributes.collect do |attr|
|
463
|
+
begin
|
464
|
+
attr.obligation(attr_validator)
|
465
|
+
rescue NotAuthorized => e
|
466
|
+
exceptions << e
|
467
|
+
nil
|
468
|
+
end
|
469
|
+
end
|
470
|
+
|
471
|
+
if exceptions.length > 0 and (@join_operator == :and or exceptions.length == @attributes.length)
|
472
|
+
raise NotAuthorized, "Missing authorization in collecting obligations: #{exceptions.map(&:to_s) * ", "}"
|
473
|
+
end
|
474
|
+
|
475
|
+
if @join_operator == :and and !obligations.empty?
|
476
|
+
# cross product of OR'ed obligations in arrays
|
477
|
+
arrayed_obligations = obligations.map {|obligation| obligation.is_a?(Hash) ? [obligation] : obligation}
|
478
|
+
merged_obligations = arrayed_obligations.first
|
479
|
+
arrayed_obligations[1..-1].each do |inner_obligations|
|
480
|
+
previous_merged_obligations = merged_obligations
|
481
|
+
merged_obligations = inner_obligations.collect do |inner_obligation|
|
482
|
+
previous_merged_obligations.collect do |merged_obligation|
|
483
|
+
merged_obligation.deep_merge(inner_obligation)
|
484
|
+
end
|
485
|
+
end.flatten
|
486
|
+
end
|
487
|
+
obligations = merged_obligations
|
488
|
+
else
|
489
|
+
obligations = obligations.flatten.compact
|
490
|
+
end
|
491
|
+
obligations.empty? ? [{}] : obligations
|
492
|
+
end
|
493
|
+
|
494
|
+
def to_long_s
|
495
|
+
attributes.collect {|attr| attr.to_long_s } * "; "
|
496
|
+
end
|
497
|
+
end
|
498
|
+
|
499
|
+
class Attribute
|
500
|
+
# attr_conditions_hash of form
|
501
|
+
# { :object_attribute => [operator, value_block], ... }
|
502
|
+
# { :object_attribute => { :attr => ... } }
|
503
|
+
def initialize (conditions_hash)
|
504
|
+
@conditions_hash = conditions_hash
|
505
|
+
end
|
506
|
+
|
507
|
+
def initialize_copy (from)
|
508
|
+
@conditions_hash = deep_hash_clone(@conditions_hash)
|
509
|
+
end
|
510
|
+
|
511
|
+
def validate? (attr_validator, object = nil, hash = nil)
|
512
|
+
object ||= attr_validator.object
|
513
|
+
return false unless object
|
514
|
+
|
515
|
+
if ( Authorization.is_a_association_proxy?(object) &&
|
516
|
+
object.respond_to?(:empty?) )
|
517
|
+
return false if object.empty?
|
518
|
+
object.each do |member|
|
519
|
+
return true if validate?(attr_validator, member, hash)
|
520
|
+
end
|
521
|
+
return false
|
522
|
+
end
|
523
|
+
|
524
|
+
(hash || @conditions_hash).all? do |attr, value|
|
525
|
+
attr_value = object_attribute_value(object, attr)
|
526
|
+
if value.is_a?(Hash)
|
527
|
+
if attr_value.is_a?(Enumerable)
|
528
|
+
attr_value.any? do |inner_value|
|
529
|
+
validate?(attr_validator, inner_value, value)
|
530
|
+
end
|
531
|
+
elsif attr_value == nil
|
532
|
+
raise NilAttributeValueError, "Attribute #{attr.inspect} is nil in #{object.inspect}."
|
533
|
+
else
|
534
|
+
validate?(attr_validator, attr_value, value)
|
535
|
+
end
|
536
|
+
elsif value.is_a?(Array) and value.length == 2 and value.first.is_a?(Symbol)
|
537
|
+
evaluated = if value[1].is_a?(Proc)
|
538
|
+
attr_validator.evaluate(value[1])
|
539
|
+
else
|
540
|
+
value[1]
|
541
|
+
end
|
542
|
+
case value[0]
|
543
|
+
when :is
|
544
|
+
attr_value == evaluated
|
545
|
+
when :is_not
|
546
|
+
attr_value != evaluated
|
547
|
+
when :contains
|
548
|
+
begin
|
549
|
+
attr_value.include?(evaluated)
|
550
|
+
rescue NoMethodError => e
|
551
|
+
raise AuthorizationUsageError, "Operator contains requires a " +
|
552
|
+
"subclass of Enumerable as attribute value, got: #{attr_value.inspect} " +
|
553
|
+
"contains #{evaluated.inspect}: #{e}"
|
554
|
+
end
|
555
|
+
when :does_not_contain
|
556
|
+
begin
|
557
|
+
!attr_value.include?(evaluated)
|
558
|
+
rescue NoMethodError => e
|
559
|
+
raise AuthorizationUsageError, "Operator does_not_contain requires a " +
|
560
|
+
"subclass of Enumerable as attribute value, got: #{attr_value.inspect} " +
|
561
|
+
"does_not_contain #{evaluated.inspect}: #{e}"
|
562
|
+
end
|
563
|
+
when :intersects_with
|
564
|
+
begin
|
565
|
+
!(evaluated.to_set & attr_value.to_set).empty?
|
566
|
+
rescue NoMethodError => e
|
567
|
+
raise AuthorizationUsageError, "Operator intersects_with requires " +
|
568
|
+
"subclasses of Enumerable, got: #{attr_value.inspect} " +
|
569
|
+
"intersects_with #{evaluated.inspect}: #{e}"
|
570
|
+
end
|
571
|
+
when :is_in
|
572
|
+
begin
|
573
|
+
evaluated.include?(attr_value)
|
574
|
+
rescue NoMethodError => e
|
575
|
+
raise AuthorizationUsageError, "Operator is_in requires a " +
|
576
|
+
"subclass of Enumerable as value, got: #{attr_value.inspect} " +
|
577
|
+
"is_in #{evaluated.inspect}: #{e}"
|
578
|
+
end
|
579
|
+
when :is_not_in
|
580
|
+
begin
|
581
|
+
!evaluated.include?(attr_value)
|
582
|
+
rescue NoMethodError => e
|
583
|
+
raise AuthorizationUsageError, "Operator is_not_in requires a " +
|
584
|
+
"subclass of Enumerable as value, got: #{attr_value.inspect} " +
|
585
|
+
"is_not_in #{evaluated.inspect}: #{e}"
|
586
|
+
end
|
587
|
+
when :lt
|
588
|
+
attr_value && attr_value < evaluated
|
589
|
+
when :lte
|
590
|
+
attr_value && attr_value <= evaluated
|
591
|
+
when :gt
|
592
|
+
attr_value && attr_value > evaluated
|
593
|
+
when :gte
|
594
|
+
attr_value && attr_value >= evaluated
|
595
|
+
else
|
596
|
+
raise AuthorizationError, "Unknown operator #{value[0]}"
|
597
|
+
end
|
598
|
+
else
|
599
|
+
raise AuthorizationError, "Wrong conditions hash format"
|
600
|
+
end
|
601
|
+
end
|
602
|
+
end
|
603
|
+
|
604
|
+
# resolves all the values in condition_hash
|
605
|
+
def obligation (attr_validator, hash = nil)
|
606
|
+
hash = (hash || @conditions_hash).clone
|
607
|
+
hash.each do |attr, value|
|
608
|
+
if value.is_a?(Hash)
|
609
|
+
hash[attr] = obligation(attr_validator, value)
|
610
|
+
elsif value.is_a?(Array) and value.length == 2
|
611
|
+
hash[attr] = [value[0], attr_validator.evaluate(value[1])]
|
612
|
+
else
|
613
|
+
raise AuthorizationError, "Wrong conditions hash format"
|
614
|
+
end
|
615
|
+
end
|
616
|
+
hash
|
617
|
+
end
|
618
|
+
|
619
|
+
def to_long_s (hash = nil)
|
620
|
+
if hash
|
621
|
+
hash.inject({}) do |memo, key_val|
|
622
|
+
key, val = key_val
|
623
|
+
memo[key] = case val
|
624
|
+
when Array then "#{val[0]} { #{val[1].respond_to?(:to_ruby) ? val[1].to_ruby.gsub(/^proc \{\n?(.*)\n?\}$/m, '\1') : "..."} }"
|
625
|
+
when Hash then to_long_s(val)
|
626
|
+
end
|
627
|
+
memo
|
628
|
+
end
|
629
|
+
else
|
630
|
+
"if_attribute #{to_long_s(@conditions_hash).inspect}"
|
631
|
+
end
|
632
|
+
end
|
633
|
+
|
634
|
+
protected
|
635
|
+
def object_attribute_value (object, attr)
|
636
|
+
begin
|
637
|
+
object.send(attr)
|
638
|
+
rescue ArgumentError, NoMethodError => e
|
639
|
+
raise AuthorizationUsageError, "Error occurred while validating attribute ##{attr} on #{object.inspect}: #{e}.\n" +
|
640
|
+
"Please check your authorization rules and ensure the attribute is correctly spelled and \n" +
|
641
|
+
"corresponds to a method on the model you are authorizing for."
|
642
|
+
end
|
643
|
+
end
|
644
|
+
|
645
|
+
def deep_hash_clone (hash)
|
646
|
+
hash.inject({}) do |memo, (key, val)|
|
647
|
+
memo[key] = case val
|
648
|
+
when Hash
|
649
|
+
deep_hash_clone(val)
|
650
|
+
when NilClass, Symbol
|
651
|
+
val
|
652
|
+
else
|
653
|
+
val.clone
|
654
|
+
end
|
655
|
+
memo
|
656
|
+
end
|
657
|
+
end
|
658
|
+
end
|
659
|
+
|
660
|
+
# An attribute condition that uses existing rules to decide validation
|
661
|
+
# and create obligations.
|
662
|
+
class AttributeWithPermission < Attribute
|
663
|
+
# E.g. privilege :read, attr_or_hash either :attribute or
|
664
|
+
# { :attribute => :deeper_attribute }
|
665
|
+
def initialize (privilege, attr_or_hash, context = nil)
|
666
|
+
@privilege = privilege
|
667
|
+
@context = context
|
668
|
+
@attr_hash = attr_or_hash
|
669
|
+
end
|
670
|
+
|
671
|
+
def initialize_copy (from)
|
672
|
+
@attr_hash = deep_hash_clone(@attr_hash) if @attr_hash.is_a?(Hash)
|
673
|
+
end
|
674
|
+
|
675
|
+
def validate? (attr_validator, object = nil, hash_or_attr = nil)
|
676
|
+
object ||= attr_validator.object
|
677
|
+
hash_or_attr ||= @attr_hash
|
678
|
+
return false unless object
|
679
|
+
|
680
|
+
case hash_or_attr
|
681
|
+
when Symbol
|
682
|
+
attr_value = object_attribute_value(object, hash_or_attr)
|
683
|
+
case attr_value
|
684
|
+
when nil
|
685
|
+
raise NilAttributeValueError, "Attribute #{hash_or_attr.inspect} is nil in #{object.inspect}."
|
686
|
+
when Enumerable
|
687
|
+
attr_value.any? do |inner_value|
|
688
|
+
attr_validator.engine.permit? @privilege, :object => inner_value, :user => attr_validator.user
|
689
|
+
end
|
690
|
+
else
|
691
|
+
attr_validator.engine.permit? @privilege, :object => attr_value, :user => attr_validator.user
|
692
|
+
end
|
693
|
+
when Hash
|
694
|
+
hash_or_attr.all? do |attr, sub_hash|
|
695
|
+
attr_value = object_attribute_value(object, attr)
|
696
|
+
if attr_value == nil
|
697
|
+
raise NilAttributeValueError, "Attribute #{attr.inspect} is nil in #{object.inspect}."
|
698
|
+
elsif attr_value.is_a?(Enumerable)
|
699
|
+
attr_value.any? do |inner_value|
|
700
|
+
validate?(attr_validator, inner_value, sub_hash)
|
701
|
+
end
|
702
|
+
else
|
703
|
+
validate?(attr_validator, attr_value, sub_hash)
|
704
|
+
end
|
705
|
+
end
|
706
|
+
when NilClass
|
707
|
+
attr_validator.engine.permit? @privilege, :object => object, :user => attr_validator.user
|
708
|
+
else
|
709
|
+
raise AuthorizationError, "Wrong conditions hash format: #{hash_or_attr.inspect}"
|
710
|
+
end
|
711
|
+
end
|
712
|
+
|
713
|
+
# may return an array of obligations to be OR'ed
|
714
|
+
def obligation (attr_validator, hash_or_attr = nil, path = [])
|
715
|
+
hash_or_attr ||= @attr_hash
|
716
|
+
case hash_or_attr
|
717
|
+
when Symbol
|
718
|
+
@context ||= begin
|
719
|
+
rule_model = attr_validator.context.to_s.classify.constantize
|
720
|
+
context_reflection = self.class.reflection_for_path(rule_model, path + [hash_or_attr])
|
721
|
+
if context_reflection.klass.respond_to?(:decl_auth_context)
|
722
|
+
context_reflection.klass.decl_auth_context
|
723
|
+
else
|
724
|
+
context_reflection.klass.name.tableize.to_sym
|
725
|
+
end
|
726
|
+
rescue # missing model, reflections
|
727
|
+
hash_or_attr.to_s.pluralize.to_sym
|
728
|
+
end
|
729
|
+
|
730
|
+
obligations = attr_validator.engine.obligations(@privilege,
|
731
|
+
:context => @context,
|
732
|
+
:user => attr_validator.user)
|
733
|
+
|
734
|
+
obligations.collect {|obl| {hash_or_attr => obl} }
|
735
|
+
when Hash
|
736
|
+
obligations_array_attrs = []
|
737
|
+
obligations =
|
738
|
+
hash_or_attr.inject({}) do |all, pair|
|
739
|
+
attr, sub_hash = pair
|
740
|
+
all[attr] = obligation(attr_validator, sub_hash, path + [attr])
|
741
|
+
if all[attr].length > 1
|
742
|
+
obligations_array_attrs << attr
|
743
|
+
else
|
744
|
+
all[attr] = all[attr].first
|
745
|
+
end
|
746
|
+
all
|
747
|
+
end
|
748
|
+
obligations = [obligations]
|
749
|
+
obligations_array_attrs.each do |attr|
|
750
|
+
next_array_size = obligations.first[attr].length
|
751
|
+
obligations = obligations.collect do |obls|
|
752
|
+
(0...next_array_size).collect do |idx|
|
753
|
+
obls_wo_array = obls.clone
|
754
|
+
obls_wo_array[attr] = obls_wo_array[attr][idx]
|
755
|
+
obls_wo_array
|
756
|
+
end
|
757
|
+
end.flatten
|
758
|
+
end
|
759
|
+
obligations
|
760
|
+
when NilClass
|
761
|
+
attr_validator.engine.obligations(@privilege,
|
762
|
+
:context => attr_validator.context,
|
763
|
+
:user => attr_validator.user)
|
764
|
+
else
|
765
|
+
raise AuthorizationError, "Wrong conditions hash format: #{hash_or_attr.inspect}"
|
766
|
+
end
|
767
|
+
end
|
768
|
+
|
769
|
+
def to_long_s
|
770
|
+
"if_permitted_to #{@privilege.inspect}, #{@attr_hash.inspect}"
|
771
|
+
end
|
772
|
+
|
773
|
+
private
|
774
|
+
def self.reflection_for_path (parent_model, path)
|
775
|
+
reflection = path.empty? ? parent_model : begin
|
776
|
+
parent = reflection_for_path(parent_model, path[0..-2])
|
777
|
+
if !parent.respond_to?(:proxy_reflection) and parent.respond_to?(:klass)
|
778
|
+
parent.klass.reflect_on_association(path.last)
|
779
|
+
else
|
780
|
+
parent.reflect_on_association(path.last)
|
781
|
+
end
|
782
|
+
rescue
|
783
|
+
parent.reflect_on_association(path.last)
|
784
|
+
end
|
785
|
+
raise "invalid path #{path.inspect}" if reflection.nil?
|
786
|
+
reflection
|
787
|
+
end
|
788
|
+
end
|
789
|
+
|
790
|
+
# Represents a pseudo-user to facilitate anonymous users in applications
|
791
|
+
class AnonymousUser
|
792
|
+
attr_reader :role_symbols
|
793
|
+
def initialize (roles = [Authorization.default_role])
|
794
|
+
@role_symbols = roles
|
795
|
+
end
|
796
|
+
end
|
797
|
+
end
|
798
|
+
|