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