ghart-declarative_authorization 0.3.2.4
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 +83 -0
- data/MIT-LICENSE +20 -0
- data/README.rdoc +510 -0
- data/Rakefile +43 -0
- data/app/controllers/authorization_rules_controller.rb +259 -0
- data/app/controllers/authorization_usages_controller.rb +23 -0
- data/app/helpers/authorization_rules_helper.rb +187 -0
- data/app/views/authorization_rules/_change.erb +58 -0
- data/app/views/authorization_rules/_show_graph.erb +37 -0
- data/app/views/authorization_rules/_suggestions.erb +48 -0
- data/app/views/authorization_rules/change.html.erb +152 -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 +634 -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 +620 -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 +597 -0
- data/lib/declarative_authorization/in_model.rb +159 -0
- data/lib/declarative_authorization/maintenance.rb +182 -0
- data/lib/declarative_authorization/obligation_scope.rb +308 -0
- data/lib/declarative_authorization/rails_legacy.rb +14 -0
- data/lib/declarative_authorization/reader.rb +441 -0
- data/test/authorization_test.rb +827 -0
- data/test/controller_filter_resource_access_test.rb +394 -0
- data/test/controller_test.rb +386 -0
- data/test/dsl_reader_test.rb +157 -0
- data/test/helper_test.rb +171 -0
- data/test/maintenance_test.rb +46 -0
- data/test/model_test.rb +1308 -0
- data/test/schema.sql +54 -0
- data/test/test_helper.rb +118 -0
- metadata +106 -0
@@ -0,0 +1,620 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), %w{development_support})
|
2
|
+
|
3
|
+
module Authorization
|
4
|
+
|
5
|
+
module DevelopmentSupport
|
6
|
+
# Ideas for improvement
|
7
|
+
# * Algorithm
|
8
|
+
# * Objective function:
|
9
|
+
# * affected user count,
|
10
|
+
# * as specific as possible (roles, privileges)
|
11
|
+
# * as little changes as necessary
|
12
|
+
# * Modify role, privilege hierarchy
|
13
|
+
# * Merge, split roles
|
14
|
+
# * Add privilege to existing rules
|
15
|
+
# * Features
|
16
|
+
# * Improve review facts: impact, affected users count
|
17
|
+
# * group similar candidates: only show abstract methods?
|
18
|
+
# * restructure GUI layout: more room for analyzing suggestions
|
19
|
+
# * changelog, previous tests, etc.
|
20
|
+
# * multiple permissions in tests
|
21
|
+
# * Evaluation of approaches with Analyzer algorithms
|
22
|
+
# * Authorization constraints
|
23
|
+
#
|
24
|
+
# Algorithm
|
25
|
+
# * for each candidate
|
26
|
+
# * abstract actions: solving first failing test (remove privilege from role)
|
27
|
+
# * for each abstract action
|
28
|
+
# * specific actions: concrete steps (remove privilege from specific role)
|
29
|
+
# * for each specific action
|
30
|
+
# * next if reversal action of previous step
|
31
|
+
# * apply specific action on candidate
|
32
|
+
# * save as solution if no failing tests on changed_candidate
|
33
|
+
# * else: queue as candidate
|
34
|
+
# * equivalent states
|
35
|
+
#
|
36
|
+
# NOTE:
|
37
|
+
# * user.clone needs to clone role_symbols
|
38
|
+
# * user.role_symbols needs to respond to <<
|
39
|
+
# * user.login is needed
|
40
|
+
#
|
41
|
+
class ChangeSupporter < AbstractAnalyzer
|
42
|
+
|
43
|
+
# Returns a list of possible approaches for changes to the current
|
44
|
+
# authorization rules that achieve a given goal. The goal is given as
|
45
|
+
# permission tests in the block. The instance method +users+ is available
|
46
|
+
# when the block is executed to refer to the then-current users, whose
|
47
|
+
# roles might have changed as one suggestion.
|
48
|
+
def find_approaches_for (options, &tests)
|
49
|
+
@prohibited_actions = (options[:prohibited_actions] || []).to_set
|
50
|
+
|
51
|
+
@approaches_by_actions = {}
|
52
|
+
|
53
|
+
candidates = []
|
54
|
+
suggestions = []
|
55
|
+
approach_checker = ApproachChecker.new(self, tests)
|
56
|
+
|
57
|
+
starting_candidate = Approach.new(@engine, options[:users], [])
|
58
|
+
if starting_candidate.check(approach_checker)
|
59
|
+
suggestions << starting_candidate
|
60
|
+
else
|
61
|
+
candidates << starting_candidate
|
62
|
+
end
|
63
|
+
|
64
|
+
checked_candidates = 0
|
65
|
+
while !candidates.empty? and checked_candidates < 200
|
66
|
+
checked_candidates += next_step(suggestions, candidates, approach_checker)
|
67
|
+
end
|
68
|
+
|
69
|
+
# remove subsets
|
70
|
+
suggestions.sort!
|
71
|
+
end
|
72
|
+
|
73
|
+
# Returns an array of GroupedApproaches for the given array of approaches.
|
74
|
+
# Only groups directly adjacent approaches
|
75
|
+
def group_approaches (approaches)
|
76
|
+
approaches.each_with_object([]) do |approach, grouped|
|
77
|
+
if grouped.last and grouped.last.approach.similar_to(approach)
|
78
|
+
grouped.last.similar_approaches << approach
|
79
|
+
else
|
80
|
+
grouped << GroupedApproach.new(approach)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
class GroupedApproach
|
86
|
+
attr_accessor :approach, :similar_approaches
|
87
|
+
def initialize (approach)
|
88
|
+
@approach = approach
|
89
|
+
@similar_approaches = []
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
class ApproachChecker
|
94
|
+
attr_reader :users, :failed_tests
|
95
|
+
|
96
|
+
def initialize (analyzer, tests)
|
97
|
+
@analyzer, @tests = analyzer, tests
|
98
|
+
end
|
99
|
+
|
100
|
+
def check (engine, users)
|
101
|
+
@current_engine = engine
|
102
|
+
@failed_tests = []
|
103
|
+
@current_test_args = nil
|
104
|
+
@current_permit_result = nil
|
105
|
+
@users = users
|
106
|
+
@ok = true
|
107
|
+
instance_eval(&@tests)
|
108
|
+
@ok
|
109
|
+
end
|
110
|
+
|
111
|
+
def assert (ok)
|
112
|
+
@failed_tests << Test.new(*([!@current_permit_result] + @current_test_args)) unless ok
|
113
|
+
@ok &&= ok
|
114
|
+
end
|
115
|
+
|
116
|
+
def permit? (*args)
|
117
|
+
@current_test_args = args
|
118
|
+
@current_permit_result = @current_engine.permit?(
|
119
|
+
*(args[0...-1] + [args.last.merge(:skip_attribute_test => true)]))
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
class Test
|
124
|
+
attr_reader :positive, :privilege, :context, :user
|
125
|
+
def initialize (positive, privilege, options = {})
|
126
|
+
@positive, @privilege = positive, privilege
|
127
|
+
@context = options[:context]
|
128
|
+
@user = options[:user]
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
class Approach
|
133
|
+
attr_reader :steps, :engine, :users, :failed_tests
|
134
|
+
def initialize (engine, users, steps)
|
135
|
+
@engine, @users, @steps = engine, users, steps
|
136
|
+
end
|
137
|
+
|
138
|
+
def check (approach_checker)
|
139
|
+
res = approach_checker.check(@engine, @users)
|
140
|
+
@failed_tests = approach_checker.failed_tests
|
141
|
+
#puts "CHECKING #{inspect} (#{res}, #{sort_value})"
|
142
|
+
res
|
143
|
+
end
|
144
|
+
|
145
|
+
def affected_users (original_engine, original_users, privilege, context)
|
146
|
+
(0...@users.length).select do |i|
|
147
|
+
original_engine.permit?(privilege, :context => context,
|
148
|
+
:skip_attribute_test => true, :user => original_users[i]) !=
|
149
|
+
@engine.permit?(privilege, :context => context,
|
150
|
+
:skip_attribute_test => true, :user => @users[i])
|
151
|
+
end.collect {|i| original_users[i]}
|
152
|
+
end
|
153
|
+
|
154
|
+
def initialize_copy (other)
|
155
|
+
@engine = @engine.clone
|
156
|
+
@users = @users.clone
|
157
|
+
@steps = @steps.clone
|
158
|
+
end
|
159
|
+
|
160
|
+
def changes
|
161
|
+
@steps
|
162
|
+
end
|
163
|
+
|
164
|
+
def abstract_actions
|
165
|
+
if failed_tests.first.positive
|
166
|
+
[
|
167
|
+
AssignPrivilegeToRoleAction,
|
168
|
+
AssignRoleToUserAction,
|
169
|
+
CreateAndAssignRoleToUserAction,
|
170
|
+
AddPrivilegeAndAssignRoleToUserAction
|
171
|
+
]
|
172
|
+
else
|
173
|
+
[
|
174
|
+
RemovePrivilegeFromRoleAction,
|
175
|
+
RemoveRoleFromUserAction
|
176
|
+
]
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
def reverse_of_previous? (specific_action)
|
181
|
+
changes.any? {|step| step.reverse?(specific_action)}
|
182
|
+
end
|
183
|
+
|
184
|
+
def apply (action)
|
185
|
+
ok = action.apply(self)
|
186
|
+
@steps << action if ok
|
187
|
+
ok
|
188
|
+
end
|
189
|
+
|
190
|
+
def subset? (other_approach)
|
191
|
+
other_approach.changes.length >= changes.length &&
|
192
|
+
changes.all? {|step| other_approach.changes.any? {|step_2| step_2.eql?(step)} }
|
193
|
+
end
|
194
|
+
|
195
|
+
def state_hash
|
196
|
+
@state_hash ||= @engine.auth_rules.inject(0) do |memo, rule|
|
197
|
+
memo + rule.privileges.hash + rule.contexts.hash +
|
198
|
+
rule.attributes.hash + rule.role.hash
|
199
|
+
end +
|
200
|
+
@users.inject(0) {|memo, user| memo + user.role_symbols.hash } +
|
201
|
+
@engine.privileges.hash + @engine.privilege_hierarchy.hash +
|
202
|
+
@engine.roles.hash + @engine.role_hierarchy.hash
|
203
|
+
end
|
204
|
+
|
205
|
+
def sort_value
|
206
|
+
weight + @failed_tests.length
|
207
|
+
end
|
208
|
+
|
209
|
+
def weight
|
210
|
+
changes.sum(&:weight)
|
211
|
+
end
|
212
|
+
|
213
|
+
def similar_to (other)
|
214
|
+
other.weight == weight and
|
215
|
+
other.changes.map {|change| change.class.name}.sort ==
|
216
|
+
changes.map {|change| change.class.name}.sort
|
217
|
+
end
|
218
|
+
|
219
|
+
def inspect
|
220
|
+
"Approach: Steps: #{changes.map(&:inspect) * ', '}"# +
|
221
|
+
# "\n Roles: #{AnalyzerEngine.roles(@engine).map(&:to_sym).inspect}; " +
|
222
|
+
# "\n Users: #{@users.map(&:role_symbols).inspect}"
|
223
|
+
end
|
224
|
+
|
225
|
+
def <=> (other)
|
226
|
+
sort_value <=> other.sort_value
|
227
|
+
end
|
228
|
+
end
|
229
|
+
|
230
|
+
class AbstractAction
|
231
|
+
def weight
|
232
|
+
1
|
233
|
+
end
|
234
|
+
|
235
|
+
# returns a list of instances of the action that may be applied
|
236
|
+
def self.specific_actions (candidate)
|
237
|
+
raise NotImplementedError, "Not yet?"
|
238
|
+
end
|
239
|
+
|
240
|
+
# applies the specific action on the given candidate
|
241
|
+
def apply (candidate)
|
242
|
+
raise NotImplementedError, "Not yet?"
|
243
|
+
end
|
244
|
+
|
245
|
+
def eql? (other)
|
246
|
+
other.class == self.class and hash == other.hash
|
247
|
+
end
|
248
|
+
|
249
|
+
def hash
|
250
|
+
@hash ||= to_a.hash
|
251
|
+
end
|
252
|
+
|
253
|
+
def reverse? (other)
|
254
|
+
false
|
255
|
+
end
|
256
|
+
|
257
|
+
def inspect
|
258
|
+
"#{self.class.name.demodulize} #{hash} #{to_a.hash} (#{to_a[1..-1].collect {|info| self.class.readable_info(info)} * ','})"
|
259
|
+
end
|
260
|
+
|
261
|
+
def to_a
|
262
|
+
[:abstract]
|
263
|
+
end
|
264
|
+
|
265
|
+
def resembles? (spec)
|
266
|
+
min_length = [spec.length, to_a.length].min
|
267
|
+
to_a[0,min_length] == spec[0,min_length]
|
268
|
+
end
|
269
|
+
|
270
|
+
def resembles_any? (specs)
|
271
|
+
specs.any? {|spec| resembles?(spec) }
|
272
|
+
end
|
273
|
+
|
274
|
+
def self.readable_info (info)
|
275
|
+
if info.respond_to?(:to_sym)
|
276
|
+
info.to_sym.inspect
|
277
|
+
else
|
278
|
+
info.inspect
|
279
|
+
end
|
280
|
+
end
|
281
|
+
end
|
282
|
+
|
283
|
+
class AbstractCompoundAction < AbstractAction
|
284
|
+
def weight
|
285
|
+
@actions.sum(&:weight) + 1
|
286
|
+
end
|
287
|
+
|
288
|
+
def apply (candidate)
|
289
|
+
@actions.all? {|action| action.apply(candidate)}
|
290
|
+
end
|
291
|
+
|
292
|
+
def reverse? (other)
|
293
|
+
@actions.any? {|action| action.reverse?(other)}
|
294
|
+
end
|
295
|
+
|
296
|
+
def to_a
|
297
|
+
@actions.inject([]) {|memo, action| memo += action.to_a.first.is_a?(Enumerable) ? action.to_a : [action.to_a]; memo }
|
298
|
+
end
|
299
|
+
|
300
|
+
def hash
|
301
|
+
@hash ||= @actions.inject(0) {|memo, action| memo += action.hash }
|
302
|
+
end
|
303
|
+
|
304
|
+
def resembles? (spec)
|
305
|
+
@actions.any? {|action| action.resembles?(spec) } or
|
306
|
+
to_a.any? do |array|
|
307
|
+
min_length = [spec.length, array.length].min
|
308
|
+
array[0,min_length] == spec[0,min_length]
|
309
|
+
end
|
310
|
+
end
|
311
|
+
end
|
312
|
+
|
313
|
+
class AssignPrivilegeToRoleAction < AbstractAction
|
314
|
+
def self.specific_actions (candidate)
|
315
|
+
privilege = AnalyzerEngine::Privilege.for_sym(
|
316
|
+
candidate.failed_tests.first.privilege, candidate.engine)
|
317
|
+
context = candidate.failed_tests.first.context
|
318
|
+
user = candidate.failed_tests.first.user
|
319
|
+
([privilege] + privilege.ancestors).collect do |ancestor_privilege|
|
320
|
+
user.role_symbols.collect {|role_sym| AnalyzerEngine::Role.for_sym(role_sym, candidate.engine) }.
|
321
|
+
collect {|role| [role] + role.ancestors}.flatten.uniq.collect do |role|
|
322
|
+
# apply checks later if privilege is already present in that role
|
323
|
+
new(ancestor_privilege.to_sym, context, role.to_sym)
|
324
|
+
end
|
325
|
+
end.flatten
|
326
|
+
end
|
327
|
+
|
328
|
+
attr_reader :privilege, :context, :role
|
329
|
+
def initialize (privilege_sym, context, role_sym)
|
330
|
+
@privilege, @context, @role = privilege_sym, context, role_sym
|
331
|
+
end
|
332
|
+
|
333
|
+
def apply (candidate)
|
334
|
+
AnalyzerEngine.apply_change(candidate.engine, to_a)
|
335
|
+
end
|
336
|
+
|
337
|
+
def reverse? (other)
|
338
|
+
other.is_a?(RemovePrivilegeFromRoleAction) and
|
339
|
+
other.privilege == @privilege and
|
340
|
+
other.context == @context and
|
341
|
+
other.role == @role
|
342
|
+
end
|
343
|
+
|
344
|
+
def to_a
|
345
|
+
[:add_privilege, @privilege, @context, @role]
|
346
|
+
end
|
347
|
+
end
|
348
|
+
|
349
|
+
class AssignRoleToUserAction < AbstractAction
|
350
|
+
def self.specific_actions (candidate)
|
351
|
+
privilege = candidate.failed_tests.first.privilege
|
352
|
+
context = candidate.failed_tests.first.context
|
353
|
+
user = candidate.failed_tests.first.user
|
354
|
+
AnalyzerEngine::Role.all_for_privilege(privilege, context, candidate.engine).collect do |role|
|
355
|
+
new(user, role.to_sym)
|
356
|
+
end
|
357
|
+
end
|
358
|
+
|
359
|
+
attr_reader :user, :role
|
360
|
+
def initialize (user, role_sym)
|
361
|
+
@user, @role = user, role_sym
|
362
|
+
end
|
363
|
+
|
364
|
+
def apply (candidate)
|
365
|
+
if candidate.engine.roles_with_hierarchy_for(@user).include?(@role)
|
366
|
+
false
|
367
|
+
else
|
368
|
+
# beware of shallow copies!
|
369
|
+
cloned_user = @user.clone
|
370
|
+
user_index = candidate.users.index(@user)
|
371
|
+
raise "Cannot find #{@user.inspect} in users array" unless user_index
|
372
|
+
candidate.users[user_index] = cloned_user
|
373
|
+
# possible on real user objects?
|
374
|
+
cloned_user.role_symbols << @role
|
375
|
+
raise "User#role_symbols immutable or user only shallowly cloned!" if cloned_user.role_symbols == @user.role_symbols
|
376
|
+
true
|
377
|
+
end
|
378
|
+
end
|
379
|
+
|
380
|
+
def hash
|
381
|
+
to_a[0,2].hash + @user.login.hash
|
382
|
+
end
|
383
|
+
|
384
|
+
def reverse? (other)
|
385
|
+
other.is_a?(RemoveRoleFromUserAction) and
|
386
|
+
other.user.login == @user.login and
|
387
|
+
other.role == @role
|
388
|
+
end
|
389
|
+
|
390
|
+
def resembles? (spec)
|
391
|
+
super(spec[0,2]) and (spec.length == 2 or spec[2] == @user.login)
|
392
|
+
end
|
393
|
+
|
394
|
+
def to_a
|
395
|
+
[:assign_role_to_user, @role, @user]
|
396
|
+
end
|
397
|
+
end
|
398
|
+
|
399
|
+
class CreateAndAssignRoleToUserAction < AbstractCompoundAction
|
400
|
+
def self.specific_actions (candidate)
|
401
|
+
privilege = AnalyzerEngine::Privilege.for_sym(
|
402
|
+
candidate.failed_tests.first.privilege, candidate.engine)
|
403
|
+
context = candidate.failed_tests.first.context
|
404
|
+
user = candidate.failed_tests.first.user
|
405
|
+
role = AnalyzerEngine::Role.for_sym(:change_supporter_new_role, candidate.engine)
|
406
|
+
([privilege] + privilege.ancestors).collect do |ancestor_privilege|
|
407
|
+
new(user, ancestor_privilege.to_sym, context, role.to_sym)
|
408
|
+
end
|
409
|
+
end
|
410
|
+
|
411
|
+
attr_reader :user, :privilege, :context, :role
|
412
|
+
def initialize (user, privilege_sym, context_sym, role_sym)
|
413
|
+
@user, @privilege, @context, @role = user, privilege_sym, context_sym, role_sym
|
414
|
+
@actions = [AddPrivilegeAndAssignRoleToUserAction.new(@user, @privilege, @context, role_sym)]
|
415
|
+
end
|
416
|
+
|
417
|
+
def apply (candidate)
|
418
|
+
if AnalyzerEngine.apply_change(candidate.engine, [:add_role, @role])
|
419
|
+
super(candidate)
|
420
|
+
else
|
421
|
+
false
|
422
|
+
end
|
423
|
+
end
|
424
|
+
|
425
|
+
def hash
|
426
|
+
to_a[0].hash + super
|
427
|
+
end
|
428
|
+
|
429
|
+
def to_a
|
430
|
+
[[:add_role, @role]] + super
|
431
|
+
end
|
432
|
+
end
|
433
|
+
|
434
|
+
class AddPrivilegeAndAssignRoleToUserAction < AbstractCompoundAction
|
435
|
+
def self.specific_actions (candidate)
|
436
|
+
privilege = AnalyzerEngine::Privilege.for_sym(
|
437
|
+
candidate.failed_tests.first.privilege, candidate.engine)
|
438
|
+
context = candidate.failed_tests.first.context
|
439
|
+
user = candidate.failed_tests.first.user
|
440
|
+
([privilege] + privilege.ancestors).collect do |ancestor_privilege|
|
441
|
+
AnalyzerEngine::Role.all(candidate.engine).collect do |role|
|
442
|
+
new(user, ancestor_privilege.to_sym, context, role.to_sym)
|
443
|
+
end
|
444
|
+
end.flatten
|
445
|
+
end
|
446
|
+
|
447
|
+
attr_reader :user, :privilege, :context, :role
|
448
|
+
def initialize (user, privilege_sym, context, role_sym)
|
449
|
+
@user, @privilege, @context, @role = user, privilege_sym, context, role_sym
|
450
|
+
@actions = [
|
451
|
+
AssignRoleToUserAction.new(@user, @role),
|
452
|
+
AssignPrivilegeToRoleAction.new(@privilege, @context, @role)
|
453
|
+
]
|
454
|
+
end
|
455
|
+
end
|
456
|
+
|
457
|
+
class RemovePrivilegeFromRoleAction < AbstractAction
|
458
|
+
def self.specific_actions (candidate)
|
459
|
+
privilege = AnalyzerEngine::Privilege.for_sym(
|
460
|
+
candidate.failed_tests.first.privilege, candidate.engine)
|
461
|
+
context = candidate.failed_tests.first.context
|
462
|
+
user = candidate.failed_tests.first.user
|
463
|
+
([privilege] + privilege.ancestors).collect do |ancestor_privilege|
|
464
|
+
user.role_symbols.collect {|role_sym| AnalyzerEngine::Role.for_sym(role_sym, candidate.engine) }.
|
465
|
+
collect {|role| [role] + role.ancestors}.flatten.uniq.collect do |role|
|
466
|
+
new(ancestor_privilege.to_sym, context, role.to_sym)
|
467
|
+
end
|
468
|
+
end.flatten
|
469
|
+
end
|
470
|
+
|
471
|
+
attr_reader :privilege, :context, :role
|
472
|
+
def initialize (privilege_sym, context, role_sym)
|
473
|
+
@privilege, @context, @role = privilege_sym, context, role_sym
|
474
|
+
end
|
475
|
+
|
476
|
+
def apply (candidate)
|
477
|
+
AnalyzerEngine.apply_change(candidate.engine, to_a)
|
478
|
+
end
|
479
|
+
|
480
|
+
def reverse? (other)
|
481
|
+
(other.is_a?(AssignPrivilegeToRoleAction) or
|
482
|
+
other.is_a?(AbstractCompoundAction)) and
|
483
|
+
other.reverse?(self)
|
484
|
+
end
|
485
|
+
|
486
|
+
def to_a
|
487
|
+
[:remove_privilege, @privilege, @context, @role]
|
488
|
+
end
|
489
|
+
end
|
490
|
+
|
491
|
+
class RemoveRoleFromUserAction < AbstractAction
|
492
|
+
def self.specific_actions (candidate)
|
493
|
+
privilege = candidate.failed_tests.first.privilege
|
494
|
+
context = candidate.failed_tests.first.context
|
495
|
+
user = candidate.failed_tests.first.user
|
496
|
+
roles_for_privilege = AnalyzerEngine::Role.all_for_privilege(privilege, context, candidate.engine).map(&:to_sym)
|
497
|
+
user.role_symbols.collect {|role_sym| AnalyzerEngine::Role.for_sym(role_sym, candidate.engine)}.
|
498
|
+
select {|role| roles_for_privilege.include?(role.to_sym)}.
|
499
|
+
collect do |role|
|
500
|
+
new(user, role.to_sym)
|
501
|
+
end
|
502
|
+
end
|
503
|
+
|
504
|
+
attr_reader :user, :role
|
505
|
+
def initialize (user, role_sym)
|
506
|
+
@user, @role = user, role_sym
|
507
|
+
end
|
508
|
+
|
509
|
+
def apply (candidate)
|
510
|
+
# beware of shallow copies!
|
511
|
+
cloned_user = @user.clone
|
512
|
+
user_index = candidate.users.index(@user)
|
513
|
+
raise "Cannot find #{@user.inspect} in users array" unless user_index
|
514
|
+
candidate.users[user_index] = cloned_user
|
515
|
+
cloned_user.role_symbols.delete(@role)
|
516
|
+
raise "User#role_symbols immutable or user only shallowly cloned!" if cloned_user.role_symbols == @user.role_symbols
|
517
|
+
true
|
518
|
+
end
|
519
|
+
|
520
|
+
def hash
|
521
|
+
to_a[0,2].hash + @user.login.hash
|
522
|
+
end
|
523
|
+
|
524
|
+
def reverse? (other)
|
525
|
+
(other.is_a?(AssignRoleToUserAction) or
|
526
|
+
other.is_a?(AbstractCompoundAction)) and
|
527
|
+
other.reverse?(self)
|
528
|
+
end
|
529
|
+
|
530
|
+
def resembles? (spec)
|
531
|
+
super(spec[0,2]) and (spec.length == 2 or spec[2] == @user.login)
|
532
|
+
end
|
533
|
+
|
534
|
+
def to_a
|
535
|
+
[:remove_role_from_user, @role, @user]
|
536
|
+
end
|
537
|
+
end
|
538
|
+
|
539
|
+
protected
|
540
|
+
def next_step (viable_approaches, candidates, approach_checker)
|
541
|
+
candidate = candidates.shift
|
542
|
+
|
543
|
+
child_candidates = generate_child_candidates(candidate)
|
544
|
+
check_child_candidates!(approach_checker, viable_approaches, candidates, child_candidates)
|
545
|
+
|
546
|
+
candidates.sort!
|
547
|
+
child_candidates.length
|
548
|
+
end
|
549
|
+
|
550
|
+
def generate_child_candidates (candidate)
|
551
|
+
child_candidates = []
|
552
|
+
abstract_actions = candidate.abstract_actions
|
553
|
+
abstract_actions.each do |abstract_action|
|
554
|
+
abstract_action.specific_actions(candidate).each do |specific_action|
|
555
|
+
child_candidate = candidate.dup
|
556
|
+
if !specific_action.resembles_any?(@prohibited_actions) and
|
557
|
+
!child_candidate.reverse_of_previous?(specific_action) and
|
558
|
+
child_candidate.apply(specific_action)
|
559
|
+
child_candidates << child_candidate
|
560
|
+
end
|
561
|
+
end
|
562
|
+
end
|
563
|
+
child_candidates
|
564
|
+
end
|
565
|
+
|
566
|
+
def check_child_candidates! (approach_checker, viable_approaches, candidates, child_candidates)
|
567
|
+
child_candidates.each do |child_candidate|
|
568
|
+
if child_candidate.check(approach_checker)
|
569
|
+
unless superset_of_existing?(child_candidate)
|
570
|
+
remove_supersets!(viable_approaches, child_candidate)
|
571
|
+
viable_approaches << child_candidate
|
572
|
+
add_to_approaches_by_action!(child_candidate)
|
573
|
+
end
|
574
|
+
else
|
575
|
+
candidates << child_candidate
|
576
|
+
end
|
577
|
+
child_candidate.freeze
|
578
|
+
end
|
579
|
+
end
|
580
|
+
|
581
|
+
def superset_of_existing? (candidate)
|
582
|
+
candidate.changes.any? do |action|
|
583
|
+
(@approaches_by_actions[action] ||= []).any? {|approach| approach.subset?(candidate)}
|
584
|
+
end
|
585
|
+
end
|
586
|
+
|
587
|
+
def remove_supersets! (existing, candidate)
|
588
|
+
candidate.changes.inject([]) do |memo, action|
|
589
|
+
memo += (@approaches_by_actions[action] ||= []).select do |approach|
|
590
|
+
candidate.subset?(approach)
|
591
|
+
end
|
592
|
+
end.uniq.each do |approach|
|
593
|
+
existing.delete(approach)
|
594
|
+
remove_from_approaches_by_action!(approach)
|
595
|
+
end
|
596
|
+
end
|
597
|
+
|
598
|
+
def add_to_approaches_by_action! (candidate)
|
599
|
+
candidate.changes.each do |action|
|
600
|
+
(@approaches_by_actions[action] ||= []) << candidate
|
601
|
+
end
|
602
|
+
end
|
603
|
+
|
604
|
+
def remove_from_approaches_by_action! (candidate)
|
605
|
+
candidate.changes.each do |action|
|
606
|
+
(@approaches_by_actions[action] ||= []).delete(candidate)
|
607
|
+
end
|
608
|
+
end
|
609
|
+
|
610
|
+
def relevant_roles (approach)
|
611
|
+
self.class.relevant_roles(approach)
|
612
|
+
end
|
613
|
+
def self.relevant_roles (approach)
|
614
|
+
(AnalyzerEngine.relevant_roles(approach.engine, approach.users) +
|
615
|
+
(approach.engine.roles.include?(:new_role_for_change_analyzer) ?
|
616
|
+
[AnalyzerEngine::Role.for_sym(:new_role_for_change_analyzer, approach.engine)] : [])).uniq
|
617
|
+
end
|
618
|
+
end
|
619
|
+
end
|
620
|
+
end
|