uhees-declarative_authorization 0.3.1
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 +77 -0
- data/MIT-LICENSE +20 -0
- data/README.rdoc +490 -0
- data/Rakefile +43 -0
- data/app/controllers/authorization_rules_controller.rb +235 -0
- data/app/controllers/authorization_usages_controller.rb +23 -0
- data/app/helpers/authorization_rules_helper.rb +183 -0
- data/app/views/authorization_rules/_change.erb +49 -0
- data/app/views/authorization_rules/_show_graph.erb +37 -0
- data/app/views/authorization_rules/_suggestion.erb +9 -0
- data/app/views/authorization_rules/_suggestions.erb +24 -0
- data/app/views/authorization_rules/change.html.erb +124 -0
- data/app/views/authorization_rules/graph.dot.erb +68 -0
- data/app/views/authorization_rules/graph.html.erb +40 -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 +7 -0
- data/garlic_example.rb +20 -0
- data/init.rb +5 -0
- data/lib/declarative_authorization.rb +15 -0
- data/lib/declarative_authorization/authorization.rb +630 -0
- data/lib/declarative_authorization/development_support/analyzer.rb +252 -0
- data/lib/declarative_authorization/development_support/change_analyzer.rb +253 -0
- data/lib/declarative_authorization/development_support/change_supporter.rb +578 -0
- data/lib/declarative_authorization/development_support/development_support.rb +243 -0
- data/lib/declarative_authorization/helper.rb +60 -0
- data/lib/declarative_authorization/in_controller.rb +367 -0
- data/lib/declarative_authorization/in_model.rb +150 -0
- data/lib/declarative_authorization/maintenance.rb +188 -0
- data/lib/declarative_authorization/obligation_scope.rb +297 -0
- data/lib/declarative_authorization/rails_legacy.rb +14 -0
- data/lib/declarative_authorization/reader.rb +438 -0
- data/test/authorization_test.rb +823 -0
- data/test/controller_test.rb +418 -0
- data/test/dsl_reader_test.rb +157 -0
- data/test/helper_test.rb +154 -0
- data/test/maintenance_test.rb +41 -0
- data/test/model_test.rb +1171 -0
- data/test/schema.sql +53 -0
- data/test/test_helper.rb +103 -0
- metadata +104 -0
@@ -0,0 +1,243 @@
|
|
1
|
+
|
2
|
+
module Authorization
|
3
|
+
module DevelopmentSupport
|
4
|
+
class AbstractAnalyzer
|
5
|
+
attr_reader :engine
|
6
|
+
|
7
|
+
def initialize (engine)
|
8
|
+
@engine = engine
|
9
|
+
end
|
10
|
+
|
11
|
+
def roles
|
12
|
+
AnalyzerEngine.roles(engine)
|
13
|
+
end
|
14
|
+
|
15
|
+
def rules
|
16
|
+
roles.collect {|role| role.rules }.flatten
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
# Groups utility methods and classes to better work with authorization object
|
21
|
+
# model.
|
22
|
+
module AnalyzerEngine
|
23
|
+
|
24
|
+
def self.roles (engine)
|
25
|
+
Role.all(engine)
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.relevant_roles (engine, users)
|
29
|
+
users.collect {|user| user.role_symbols.map {|role_sym| Role.for_sym(role_sym, engine)}}.
|
30
|
+
flatten.uniq.collect {|role| [role] + role.ancestors}.flatten.uniq
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.rule_for_permission (engine, privilege, context, role)
|
34
|
+
AnalyzerEngine.roles(engine).
|
35
|
+
find {|cloned_role| cloned_role.to_sym == role.to_sym}.rules.find do |rule|
|
36
|
+
rule.contexts.include?(context) and rule.privileges.include?(privilege)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def self.apply_change (engine, change)
|
41
|
+
case change[0]
|
42
|
+
when :add_role
|
43
|
+
role_symbol = change[1]
|
44
|
+
if engine.roles.include?(role_symbol)
|
45
|
+
false
|
46
|
+
else
|
47
|
+
engine.roles << role_symbol
|
48
|
+
true
|
49
|
+
end
|
50
|
+
when :add_privilege
|
51
|
+
privilege, context, role = change[1,3]
|
52
|
+
role = Role.for_sym(role.to_sym, engine)
|
53
|
+
privilege = Privilege.for_sym(privilege.to_sym, engine)
|
54
|
+
if ([privilege] + privilege.ancestors).any? {|ancestor_privilege| ([role] + role.ancestors).any? {|ancestor_role| !ancestor_role.rules_for_permission(ancestor_privilege, context).empty?}}
|
55
|
+
false
|
56
|
+
else
|
57
|
+
engine.auth_rules << AuthorizationRule.new(role.to_sym,
|
58
|
+
[privilege.to_sym], [context])
|
59
|
+
true
|
60
|
+
end
|
61
|
+
when :remove_privilege
|
62
|
+
privilege, context, role = change[1,3]
|
63
|
+
role = Role.for_sym(role.to_sym, engine)
|
64
|
+
privilege = Privilege.for_sym(privilege.to_sym, engine)
|
65
|
+
rules_with_priv = role.rules_for_permission(privilege, context)
|
66
|
+
if rules_with_priv.empty?
|
67
|
+
false
|
68
|
+
else
|
69
|
+
rules_with_priv.each do |rule|
|
70
|
+
rule.rule.privileges.delete(privilege.to_sym)
|
71
|
+
engine.auth_rules.delete(rule.rule) if rule.rule.privileges.empty?
|
72
|
+
end
|
73
|
+
true
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
class Role
|
79
|
+
@@role_objects = {}
|
80
|
+
attr_reader :role
|
81
|
+
def initialize (role, rules, engine)
|
82
|
+
@role = role
|
83
|
+
@rules = rules
|
84
|
+
@engine = engine
|
85
|
+
end
|
86
|
+
|
87
|
+
def source_line
|
88
|
+
@rules.empty? ? nil : @rules.first.source_line
|
89
|
+
end
|
90
|
+
def source_file
|
91
|
+
@rules.empty? ? nil : @rules.first.source_file
|
92
|
+
end
|
93
|
+
|
94
|
+
# ancestors' privileges are included in in the current role
|
95
|
+
def ancestors (role_symbol = nil)
|
96
|
+
role_symbol ||= @role
|
97
|
+
(@engine.role_hierarchy[role_symbol] || []).
|
98
|
+
collect {|lower_role| ancestors(lower_role) }.flatten +
|
99
|
+
(role_symbol == @role ? [] : [Role.for_sym(role_symbol, @engine)])
|
100
|
+
end
|
101
|
+
def descendants (role_symbol = nil)
|
102
|
+
role_symbol ||= @role
|
103
|
+
(@engine.rev_role_hierarchy[role_symbol] || []).
|
104
|
+
collect {|higher_role| descendants(higher_role) }.flatten +
|
105
|
+
(role_symbol == @role ? [] : [Role.for_sym(role_symbol, @engine)])
|
106
|
+
end
|
107
|
+
|
108
|
+
def rules
|
109
|
+
@rules ||= @engine.auth_rules.select {|rule| rule.role == @role}.
|
110
|
+
collect {|rule| Rule.new(rule, @engine)}
|
111
|
+
end
|
112
|
+
def rules_for_permission (privilege, context)
|
113
|
+
rules.select do |rule|
|
114
|
+
rule.matches?([@role], [privilege.to_sym], context)
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
def to_sym
|
119
|
+
@role
|
120
|
+
end
|
121
|
+
def self.for_sym (role_sym, engine)
|
122
|
+
@@role_objects[[role_sym, engine]] ||= new(role_sym, nil, engine)
|
123
|
+
end
|
124
|
+
|
125
|
+
def self.all (engine)
|
126
|
+
rules_by_role = engine.auth_rules.inject({}) do |memo, rule|
|
127
|
+
memo[rule.role] ||= []
|
128
|
+
memo[rule.role] << rule
|
129
|
+
memo
|
130
|
+
end
|
131
|
+
engine.roles.collect do |role|
|
132
|
+
new(role, (rules_by_role[role] || []).
|
133
|
+
collect {|rule| Rule.new(rule, engine)}, engine)
|
134
|
+
end
|
135
|
+
end
|
136
|
+
def self.all_for_privilege (privilege, context, engine)
|
137
|
+
privilege = privilege.is_a?(Symbol) ? Privilege.for_sym(privilege, engine) : privilege
|
138
|
+
privilege_symbols = ([privilege] + privilege.ancestors).map(&:to_sym)
|
139
|
+
all(engine).select {|role| role.rules.any? {|rule| rule.matches?([role.to_sym], privilege_symbols, context)}}.
|
140
|
+
collect {|role| [role] + role.descendants}.flatten.uniq
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
class Rule
|
145
|
+
@@rule_objects = {}
|
146
|
+
delegate :source_line, :source_file, :contexts, :matches?, :to => :@rule
|
147
|
+
attr_reader :rule
|
148
|
+
def initialize (rule, engine)
|
149
|
+
@rule = rule
|
150
|
+
@engine = engine
|
151
|
+
end
|
152
|
+
def privileges
|
153
|
+
PrivilegesSet.new(self, @engine, @rule.privileges.collect {|privilege| Privilege.for_sym(privilege, @engine) })
|
154
|
+
end
|
155
|
+
def self.for_rule (rule, engine)
|
156
|
+
@@rule_objects[[rule, engine]] ||= new(rule, engine)
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
class Privilege
|
161
|
+
@@privilege_objects = {}
|
162
|
+
def initialize (privilege, engine)
|
163
|
+
@privilege = privilege
|
164
|
+
@engine = engine
|
165
|
+
end
|
166
|
+
|
167
|
+
# Ancestor privileges are higher in the hierarchy.
|
168
|
+
# Doesn't take context into account.
|
169
|
+
def ancestors (priv_symbol = nil)
|
170
|
+
priv_symbol ||= @privilege
|
171
|
+
# context-specific?
|
172
|
+
(@engine.rev_priv_hierarchy[[priv_symbol, nil]] || []).
|
173
|
+
collect {|higher_priv| ancestors(higher_priv) }.flatten +
|
174
|
+
(priv_symbol == @privilege ? [] : [Privilege.for_sym(priv_symbol, @engine)])
|
175
|
+
end
|
176
|
+
def descendants (priv_symbol = nil)
|
177
|
+
priv_symbol ||= @privilege
|
178
|
+
# context-specific?
|
179
|
+
(@engine.privilege_hierarchy[priv_symbol] || []).
|
180
|
+
collect {|lower_priv, context| descendants(lower_priv) }.flatten +
|
181
|
+
(priv_symbol == @privilege ? [] : [Privilege.for_sym(priv_symbol, @engine)])
|
182
|
+
end
|
183
|
+
|
184
|
+
def rules
|
185
|
+
@rules ||= find_rules_for_privilege
|
186
|
+
end
|
187
|
+
def source_line
|
188
|
+
rules.empty? ? nil : rules.first.source_line
|
189
|
+
end
|
190
|
+
def source_file
|
191
|
+
rules.empty? ? nil : rules.first.source_file
|
192
|
+
end
|
193
|
+
|
194
|
+
def to_sym
|
195
|
+
@privilege
|
196
|
+
end
|
197
|
+
def self.for_sym (privilege_sym, engine)
|
198
|
+
@@privilege_objects[[privilege_sym, engine]] ||= new(privilege_sym, engine)
|
199
|
+
end
|
200
|
+
|
201
|
+
private
|
202
|
+
def find_rules_for_privilege
|
203
|
+
@engine.auth_rules.select {|rule| rule.privileges.include?(@privilege)}.
|
204
|
+
collect {|rule| Rule.for_rule(rule, @engine)}
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
208
|
+
class PrivilegesSet < Set
|
209
|
+
def initialize (*args)
|
210
|
+
if args.length > 2
|
211
|
+
@rule = args.shift
|
212
|
+
@engine = args.shift
|
213
|
+
end
|
214
|
+
super(*args)
|
215
|
+
end
|
216
|
+
def include? (privilege)
|
217
|
+
if privilege.is_a?(Symbol)
|
218
|
+
super(privilege_from_symbol(privilege))
|
219
|
+
else
|
220
|
+
super
|
221
|
+
end
|
222
|
+
end
|
223
|
+
def delete (privilege)
|
224
|
+
@rule.rule.privileges.delete(privilege.to_sym)
|
225
|
+
if privilege.is_a?(Symbol)
|
226
|
+
super(privilege_from_symbol(privilege))
|
227
|
+
else
|
228
|
+
super
|
229
|
+
end
|
230
|
+
end
|
231
|
+
|
232
|
+
def intersects? (privileges)
|
233
|
+
intersection(privileges).length > 0
|
234
|
+
end
|
235
|
+
|
236
|
+
private
|
237
|
+
def privilege_from_symbol (privilege_sym)
|
238
|
+
Privilege.for_sym(privilege_sym, @engine)
|
239
|
+
end
|
240
|
+
end
|
241
|
+
end
|
242
|
+
end
|
243
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
# Authorization::AuthorizationHelper
|
2
|
+
require File.dirname(__FILE__) + '/authorization.rb'
|
3
|
+
|
4
|
+
module Authorization
|
5
|
+
module AuthorizationHelper
|
6
|
+
|
7
|
+
# If the current user meets the given privilege, permitted_to? returns true
|
8
|
+
# and yields to the optional block. The attribute checks that are defined
|
9
|
+
# in the authorization rules are only evaluated if an object is given
|
10
|
+
# for context.
|
11
|
+
#
|
12
|
+
# Examples:
|
13
|
+
# <% permitted_to? :create, :users do %>
|
14
|
+
# <%= link_to 'New', new_user_path %>
|
15
|
+
# <% end %>
|
16
|
+
# ...
|
17
|
+
# <% if permitted_to? :create, :users %>
|
18
|
+
# <%= link_to 'New', new_user_path %>
|
19
|
+
# <% else %>
|
20
|
+
# You are not allowed to create new users!
|
21
|
+
# <% end %>
|
22
|
+
# ...
|
23
|
+
# <% for user in @users %>
|
24
|
+
# <%= link_to 'Edit', edit_user_path(user) if permitted_to? :update, user %>
|
25
|
+
# <% end %>
|
26
|
+
#
|
27
|
+
# To pass in an object and override the context, you can use the optional
|
28
|
+
# options:
|
29
|
+
# permitted_to? :update, user, :context => :account
|
30
|
+
#
|
31
|
+
def permitted_to? (privilege, object_or_sym = nil, options = {}, &block)
|
32
|
+
controller.permitted_to?(privilege, object_or_sym, options, &block)
|
33
|
+
end
|
34
|
+
|
35
|
+
# While permitted_to? is used for authorization in views, in some cases
|
36
|
+
# content should only be shown to some users without being concerned
|
37
|
+
# with authorization. E.g. to only show the most relevant menu options
|
38
|
+
# to a certain group of users. That is what has_role? should be used for.
|
39
|
+
#
|
40
|
+
# Examples:
|
41
|
+
# <% has_role?(:sales) do %>
|
42
|
+
# <%= link_to 'All contacts', contacts_path %>
|
43
|
+
# <% end %>
|
44
|
+
# ...
|
45
|
+
# <% if has_role?(:sales) %>
|
46
|
+
# <%= link_to 'Customer contacts', contacts_path %>
|
47
|
+
# <% else %>
|
48
|
+
# ...
|
49
|
+
# <% end %>
|
50
|
+
#
|
51
|
+
def has_role? (*roles, &block)
|
52
|
+
controller.has_role?(*roles, &block)
|
53
|
+
end
|
54
|
+
|
55
|
+
# As has_role? except checks all roles included in the role hierarchy
|
56
|
+
def has_role_with_hierarchy?(*roles, &block)
|
57
|
+
controller.has_role_with_hierarchy?(*roles, &block)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,367 @@
|
|
1
|
+
# Authorization::AuthorizationInController
|
2
|
+
require File.dirname(__FILE__) + '/authorization.rb'
|
3
|
+
|
4
|
+
module Authorization
|
5
|
+
module AuthorizationInController
|
6
|
+
|
7
|
+
def self.included(base) # :nodoc:
|
8
|
+
base.extend(ClassMethods)
|
9
|
+
base.hide_action :authorization_engine, :permitted_to?,
|
10
|
+
:permitted_to!
|
11
|
+
end
|
12
|
+
|
13
|
+
DEFAULT_DENY = false
|
14
|
+
|
15
|
+
# Returns the Authorization::Engine for the current controller.
|
16
|
+
def authorization_engine
|
17
|
+
@authorization_engine ||= Authorization::Engine.instance
|
18
|
+
end
|
19
|
+
|
20
|
+
# If the current user meets the given privilege, permitted_to? returns true
|
21
|
+
# and yields to the optional block. The attribute checks that are defined
|
22
|
+
# in the authorization rules are only evaluated if an object is given
|
23
|
+
# for context.
|
24
|
+
#
|
25
|
+
# See examples for Authorization::AuthorizationHelper #permitted_to?
|
26
|
+
#
|
27
|
+
# If no object or context is specified, the controller_name is used as
|
28
|
+
# context.
|
29
|
+
#
|
30
|
+
def permitted_to? (privilege, object_or_sym = nil, options = {}, &block)
|
31
|
+
permitted_to!(privilege, object_or_sym, options.merge(:non_bang => true), &block)
|
32
|
+
end
|
33
|
+
|
34
|
+
# Works similar to the permitted_to? method, but
|
35
|
+
# throws the authorization exceptions, just like Engine#permit!
|
36
|
+
def permitted_to! (privilege, object_or_sym = nil, options = {}, &block)
|
37
|
+
context = object = nil
|
38
|
+
if object_or_sym.nil?
|
39
|
+
context = self.class.controller_name.to_sym
|
40
|
+
elsif object_or_sym.is_a?(Symbol)
|
41
|
+
context = object_or_sym
|
42
|
+
else
|
43
|
+
object = object_or_sym
|
44
|
+
end
|
45
|
+
|
46
|
+
non_bang = options.delete(:non_bang)
|
47
|
+
args = [
|
48
|
+
privilege,
|
49
|
+
{:user => current_user,
|
50
|
+
:object => object,
|
51
|
+
:context => context,
|
52
|
+
:skip_attribute_test => object.nil?}.merge(options)
|
53
|
+
]
|
54
|
+
if non_bang
|
55
|
+
authorization_engine.permit?(*args, &block)
|
56
|
+
else
|
57
|
+
authorization_engine.permit!(*args, &block)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
# While permitted_to? is used for authorization, in some cases
|
62
|
+
# content should only be shown to some users without being concerned
|
63
|
+
# with authorization. E.g. to only show the most relevant menu options
|
64
|
+
# to a certain group of users. That is what has_role? should be used for.
|
65
|
+
def has_role? (*roles, &block)
|
66
|
+
user_roles = authorization_engine.roles_for(current_user)
|
67
|
+
result = roles.all? do |role|
|
68
|
+
user_roles.include?(role)
|
69
|
+
end
|
70
|
+
yield if result and block_given?
|
71
|
+
result
|
72
|
+
end
|
73
|
+
|
74
|
+
# As has_role? except checks all roles included in the role hierarchy
|
75
|
+
def has_role_with_hierarchy?(*roles, &block)
|
76
|
+
user_roles = authorization_engine.roles_with_hierarchy_for(current_user)
|
77
|
+
result = roles.all? do |role|
|
78
|
+
user_roles.include?(role)
|
79
|
+
end
|
80
|
+
yield if result and block_given?
|
81
|
+
result
|
82
|
+
end
|
83
|
+
|
84
|
+
|
85
|
+
protected
|
86
|
+
def filter_access_filter # :nodoc:
|
87
|
+
permissions = self.class.all_filter_access_permissions
|
88
|
+
all_permissions = permissions.select {|p| p.actions.include?(:all)}
|
89
|
+
matching_permissions = permissions.select {|p| p.matches?(action_name)}
|
90
|
+
allowed = false
|
91
|
+
auth_exception = nil
|
92
|
+
begin
|
93
|
+
allowed = if !matching_permissions.empty?
|
94
|
+
matching_permissions.all? {|perm| perm.permit!(self)}
|
95
|
+
elsif !all_permissions.empty?
|
96
|
+
all_permissions.all? {|perm| perm.permit!(self)}
|
97
|
+
else
|
98
|
+
!DEFAULT_DENY
|
99
|
+
end
|
100
|
+
rescue AuthorizationError => e
|
101
|
+
auth_exception = e
|
102
|
+
end
|
103
|
+
|
104
|
+
unless allowed
|
105
|
+
if all_permissions.empty? and matching_permissions.empty?
|
106
|
+
logger.warn "Permission denied: No matching filter access " +
|
107
|
+
"rule found for #{self.class.controller_name}.#{action_name}"
|
108
|
+
elsif auth_exception
|
109
|
+
logger.info "Permission denied: #{auth_exception}"
|
110
|
+
end
|
111
|
+
if respond_to?(:permission_denied)
|
112
|
+
# permission_denied needs to render or redirect
|
113
|
+
send(:permission_denied)
|
114
|
+
else
|
115
|
+
send(:render, :text => "You are not allowed to access this action.",
|
116
|
+
:status => :forbidden)
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
module ClassMethods
|
122
|
+
#
|
123
|
+
# Defines a filter to be applied according to the authorization of the
|
124
|
+
# current user. Requires at least one symbol corresponding to an
|
125
|
+
# action as parameter. The special symbol :+all+ refers to all action.
|
126
|
+
# The all :+all+ statement is only employed if no specific statement is
|
127
|
+
# present.
|
128
|
+
# class UserController < ApplicationController
|
129
|
+
# filter_access_to :index
|
130
|
+
# filter_access_to :new, :edit
|
131
|
+
# filter_access_to :all
|
132
|
+
# ...
|
133
|
+
# end
|
134
|
+
#
|
135
|
+
# The default is to allow access unconditionally if no rule matches.
|
136
|
+
# Thus, including the +filter_access_to+ :+all+ statement is a good
|
137
|
+
# idea, implementing a default-deny policy.
|
138
|
+
#
|
139
|
+
# When the access is denied, the method +permission_denied+ is called
|
140
|
+
# on the current controller, if defined. Else, a simple "you are not
|
141
|
+
# allowed" string is output. Log.info is given more information on the
|
142
|
+
# reasons of denial.
|
143
|
+
#
|
144
|
+
# def permission_denied
|
145
|
+
# flash[:error] = 'Sorry, you are not allowed to the requested page.'
|
146
|
+
# respond_to do |format|
|
147
|
+
# format.html { redirect_to(:back) rescue redirect_to('/') }
|
148
|
+
# format.xml { head :unauthorized }
|
149
|
+
# format.js { head :unauthorized }
|
150
|
+
# end
|
151
|
+
# end
|
152
|
+
#
|
153
|
+
# By default, required privileges are infered from the action name and
|
154
|
+
# the controller name. Thus, in UserController :+edit+ requires
|
155
|
+
# :+edit+ +users+. To specify required privilege, use the option :+require+
|
156
|
+
# filter_access_to :new, :create, :require => :create, :context => :users
|
157
|
+
#
|
158
|
+
# Without the :+attribute_check+ option, no constraints from the
|
159
|
+
# authorization rules are enforced because for some actions (collections,
|
160
|
+
# +new+, +create+), there is no object to evaluate conditions against. To
|
161
|
+
# allow attribute checks on all actions, it is a common pattern to provide
|
162
|
+
# custom objects through +before_filters+:
|
163
|
+
# class BranchesController < ApplicationController
|
164
|
+
# before_filter :load_company
|
165
|
+
# before_filter :new_branch_from_company_and_params,
|
166
|
+
# :only => [:index, :new, :create]
|
167
|
+
# filter_access_to :all, :attribute_check => true
|
168
|
+
#
|
169
|
+
# protected
|
170
|
+
# def new_branch_from_company_and_params
|
171
|
+
# @branch = @company.branches.new(params[:branch])
|
172
|
+
# end
|
173
|
+
# end
|
174
|
+
# NOTE: +before_filters+ need to be defined before the first
|
175
|
+
# +filter_access_to+ call.
|
176
|
+
#
|
177
|
+
# For further customization, a custom filter expression may be formulated
|
178
|
+
# in a block, which is then evaluated in the context of the controller
|
179
|
+
# on a matching request. That is, for checking two objects, use the
|
180
|
+
# following:
|
181
|
+
# filter_access_to :merge do
|
182
|
+
# permitted_to!(:update, User.find(params[:original_id])) and
|
183
|
+
# permitted_to!(:delete, User.find(params[:id]))
|
184
|
+
# end
|
185
|
+
# The block should raise a Authorization::AuthorizationError or return
|
186
|
+
# false if the access is to be denied.
|
187
|
+
#
|
188
|
+
# Later calls to filter_access_to with overlapping actions overwrite
|
189
|
+
# previous ones for that action.
|
190
|
+
#
|
191
|
+
# All options:
|
192
|
+
# [:+require+]
|
193
|
+
# Privilege required; defaults to action_name
|
194
|
+
# [:+context+]
|
195
|
+
# The privilege's context, defaults to controller_name, pluralized.
|
196
|
+
# [:+namespace+]
|
197
|
+
# Prefix the default controller context with.
|
198
|
+
# * +true+: the model namespace(s) separated with underscores,
|
199
|
+
# * +Symbol+ or +String+: the given symbol or string
|
200
|
+
# * else: no prefix
|
201
|
+
# Example:
|
202
|
+
# filter_access_to :show, :namespace => true
|
203
|
+
# filter_access_to :delete, :namespace => :foo
|
204
|
+
# [:+attribute_check+]
|
205
|
+
# Enables the check of attributes defined in the authorization rules.
|
206
|
+
# Defaults to false. If enabled, filter_access_to will use a context
|
207
|
+
# object from one of the following sources (in that order):
|
208
|
+
# * the method from the :+load_method+ option,
|
209
|
+
# * an instance variable named after the singular of the context
|
210
|
+
# (by default from the controller name, e.g. @post for PostsController),
|
211
|
+
# * a find on the context model, using +params+[:id] as id value.
|
212
|
+
# Any of these methods will only be employed if :+attribute_check+
|
213
|
+
# is enabled.
|
214
|
+
# [:+model+]
|
215
|
+
# The data model to load a context object from. Defaults to the
|
216
|
+
# context, singularized.
|
217
|
+
# [:+load_method+]
|
218
|
+
# Specify a method by symbol or a Proc object which should be used
|
219
|
+
# to load the object. Both should return the loaded object.
|
220
|
+
# If a Proc object is given, e.g. by way of
|
221
|
+
# +lambda+, it is called in the instance of the controller.
|
222
|
+
# Example demonstrating the default behaviour:
|
223
|
+
# filter_access_to :show, :attribute_check => true,
|
224
|
+
# :load_method => lambda { User.find(params[:id]) }
|
225
|
+
#
|
226
|
+
|
227
|
+
def filter_access_to (*args, &filter_block)
|
228
|
+
options = args.last.is_a?(Hash) ? args.pop : {}
|
229
|
+
options = {
|
230
|
+
:require => nil,
|
231
|
+
:context => nil,
|
232
|
+
:namespace => nil,
|
233
|
+
:attribute_check => false,
|
234
|
+
:model => nil,
|
235
|
+
:load_method => nil
|
236
|
+
}.merge!(options)
|
237
|
+
privilege = options[:require]
|
238
|
+
context = options[:context]
|
239
|
+
actions = args.flatten
|
240
|
+
|
241
|
+
# collect permits in controller array for use in one before_filter
|
242
|
+
unless filter_chain.any? {|filter| filter.method == :filter_access_filter}
|
243
|
+
before_filter :filter_access_filter
|
244
|
+
end
|
245
|
+
|
246
|
+
filter_access_permissions.each do |perm|
|
247
|
+
perm.remove_actions(actions)
|
248
|
+
end
|
249
|
+
filter_access_permissions <<
|
250
|
+
ControllerPermission.new(actions, privilege, context,
|
251
|
+
options[:namespace],
|
252
|
+
options[:attribute_check],
|
253
|
+
options[:model],
|
254
|
+
options[:load_method],
|
255
|
+
filter_block)
|
256
|
+
end
|
257
|
+
|
258
|
+
# Collecting all the ControllerPermission objects from the controller
|
259
|
+
# hierarchy. Permissions for actions are overwritten by calls to
|
260
|
+
# filter_access_to in child controllers with the same action.
|
261
|
+
def all_filter_access_permissions # :nodoc:
|
262
|
+
ancestors.inject([]) do |perms, mod|
|
263
|
+
if mod.respond_to?(:filter_access_permissions)
|
264
|
+
perms +
|
265
|
+
mod.filter_access_permissions.collect do |p1|
|
266
|
+
p1.clone.remove_actions(perms.inject(Set.new) {|actions, p2| actions + p2.actions})
|
267
|
+
end
|
268
|
+
else
|
269
|
+
perms
|
270
|
+
end
|
271
|
+
end
|
272
|
+
end
|
273
|
+
|
274
|
+
protected
|
275
|
+
def filter_access_permissions # :nodoc:
|
276
|
+
unless filter_access_permissions?
|
277
|
+
ancestors[1..-1].reverse.each do |mod|
|
278
|
+
mod.filter_access_permissions if mod.respond_to?(:filter_access_permissions)
|
279
|
+
end
|
280
|
+
end
|
281
|
+
class_variable_set(:@@declarative_authorization_permissions, {}) unless filter_access_permissions?
|
282
|
+
class_variable_get(:@@declarative_authorization_permissions)[self.name] ||= []
|
283
|
+
end
|
284
|
+
|
285
|
+
def filter_access_permissions? # :nodoc:
|
286
|
+
class_variable_defined?(:@@declarative_authorization_permissions)
|
287
|
+
end
|
288
|
+
end
|
289
|
+
end
|
290
|
+
|
291
|
+
class ControllerPermission # :nodoc:
|
292
|
+
attr_reader :actions, :privilege, :context, :namespace, :attribute_check
|
293
|
+
def initialize (actions, privilege, context, namespace, attribute_check = false,
|
294
|
+
load_object_model = nil, load_object_method = nil,
|
295
|
+
filter_block = nil)
|
296
|
+
@actions = actions.to_set
|
297
|
+
@privilege = privilege
|
298
|
+
@context = context
|
299
|
+
@namespace = namespace
|
300
|
+
@load_object_model = load_object_model
|
301
|
+
@load_object_method = load_object_method
|
302
|
+
@filter_block = filter_block
|
303
|
+
@attribute_check = attribute_check
|
304
|
+
end
|
305
|
+
|
306
|
+
def controller_context(contr)
|
307
|
+
case @namespace
|
308
|
+
when true
|
309
|
+
"#{contr.class.name.gsub(/::/, "_").gsub(/Controller$/, "").underscore}".to_sym
|
310
|
+
when String, Symbol
|
311
|
+
"#{@namespace.to_s}_#{contr.class.controller_name}".to_sym
|
312
|
+
else
|
313
|
+
contr.class.controller_name.to_sym
|
314
|
+
end
|
315
|
+
end
|
316
|
+
|
317
|
+
def matches? (action_name)
|
318
|
+
@actions.include?(action_name.to_sym)
|
319
|
+
end
|
320
|
+
|
321
|
+
def permit! (contr)
|
322
|
+
if @filter_block
|
323
|
+
return contr.instance_eval(&@filter_block)
|
324
|
+
end
|
325
|
+
context = @context || controller_context(contr)
|
326
|
+
object = @attribute_check ? load_object(contr, context) : nil
|
327
|
+
privilege = @privilege || :"#{contr.action_name}"
|
328
|
+
|
329
|
+
#puts "Trying permit?(#{privilege.inspect}, "
|
330
|
+
#puts " :user => #{contr.send(:current_user).inspect}, "
|
331
|
+
#puts " :object => #{object.inspect},"
|
332
|
+
#puts " :skip_attribute_test => #{!@attribute_check},"
|
333
|
+
#puts " :context => #{contr.class.controller_name.pluralize.to_sym})"
|
334
|
+
res = contr.authorization_engine.permit!(privilege,
|
335
|
+
:user => contr.send(:current_user),
|
336
|
+
:object => object,
|
337
|
+
:skip_attribute_test => !@attribute_check,
|
338
|
+
:context => context)
|
339
|
+
#puts "permit? result: #{res.inspect}"
|
340
|
+
res
|
341
|
+
end
|
342
|
+
|
343
|
+
def remove_actions (actions)
|
344
|
+
@actions -= actions
|
345
|
+
self
|
346
|
+
end
|
347
|
+
|
348
|
+
private
|
349
|
+
def load_object(contr, context)
|
350
|
+
if @load_object_method and @load_object_method.is_a?(Symbol)
|
351
|
+
contr.send(@load_object_method)
|
352
|
+
elsif @load_object_method and @load_object_method.is_a?(Proc)
|
353
|
+
contr.instance_eval(&@load_object_method)
|
354
|
+
else
|
355
|
+
load_object_model = @load_object_model || context.to_s.classify.constantize
|
356
|
+
instance_var = :"@#{load_object_model.name.underscore}"
|
357
|
+
object = contr.instance_variable_get(instance_var)
|
358
|
+
unless object
|
359
|
+
# catch ActiveRecord::RecordNotFound?
|
360
|
+
object = load_object_model.find(contr.params[:id])
|
361
|
+
contr.instance_variable_set(instance_var, object)
|
362
|
+
end
|
363
|
+
object
|
364
|
+
end
|
365
|
+
end
|
366
|
+
end
|
367
|
+
end
|