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,252 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), %w{development_support})
|
2
|
+
|
3
|
+
begin
|
4
|
+
require "ruby_parser"
|
5
|
+
#require "parse_tree"
|
6
|
+
#require "parse_tree_extensions"
|
7
|
+
require "sexp_processor"
|
8
|
+
rescue LoadError
|
9
|
+
raise "Authorization::DevelopmentSupport::Analyzer requires ruby_parser gem"
|
10
|
+
end
|
11
|
+
|
12
|
+
module Authorization
|
13
|
+
|
14
|
+
module DevelopmentSupport
|
15
|
+
|
16
|
+
# Ideas for improvement
|
17
|
+
# * moving rules up in the role hierarchy
|
18
|
+
# * merging roles
|
19
|
+
# * role hierarchy
|
20
|
+
#
|
21
|
+
# Mergeable Rules: respect if_permitted_to hash
|
22
|
+
#
|
23
|
+
class Analyzer < AbstractAnalyzer
|
24
|
+
def analyze (rules)
|
25
|
+
sexp_array = RubyParser.new.parse(rules)
|
26
|
+
#sexp_array = ParseTree.translate(rules)
|
27
|
+
@reports = []
|
28
|
+
[MergeableRulesProcessor].each do |parser|
|
29
|
+
parser.new(self).analyze(sexp_array)
|
30
|
+
end
|
31
|
+
[
|
32
|
+
RoleExplosionAnalyzer, InheritingPrivilegesAnalyzer,
|
33
|
+
ProposedPrivilegeHierarchyAnalyzer
|
34
|
+
].each do |parser|
|
35
|
+
parser.new(self).analyze
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def reports
|
40
|
+
@reports or raise "No rules analyzed!"
|
41
|
+
end
|
42
|
+
|
43
|
+
class GeneralRulesAnalyzer
|
44
|
+
def initialize(analyzer)
|
45
|
+
@analyzer = analyzer
|
46
|
+
end
|
47
|
+
|
48
|
+
def analyze
|
49
|
+
mark(:policy, nil) if analyze_policy
|
50
|
+
roles.select {|role| analyze_role(role) }.
|
51
|
+
each { |role| mark(:role, role) }
|
52
|
+
rules.select {|rule| analyze_rule(rule) }.
|
53
|
+
each { |rule| mark(:rule, rule) }
|
54
|
+
privileges.select {|privilege| !!analyze_privilege(privilege) }.
|
55
|
+
each { |privilege| mark(:privilege, privilege) }
|
56
|
+
end
|
57
|
+
|
58
|
+
protected
|
59
|
+
def roles
|
60
|
+
@analyzer.roles
|
61
|
+
end
|
62
|
+
|
63
|
+
def rules
|
64
|
+
@analyzer.rules
|
65
|
+
end
|
66
|
+
|
67
|
+
def privileges
|
68
|
+
@privileges ||= rules.collect {|rule| rule.privileges.to_a}.flatten.uniq
|
69
|
+
end
|
70
|
+
|
71
|
+
# to be implemented by specific processor
|
72
|
+
def analyze_policy; end
|
73
|
+
def analyze_role (a_role); end
|
74
|
+
def analyze_rule (a_rule); end
|
75
|
+
def analyze_privilege (a_privilege); end
|
76
|
+
def message (object); end
|
77
|
+
|
78
|
+
private
|
79
|
+
def source_line (object)
|
80
|
+
object.source_line if object.respond_to?(:source_line)
|
81
|
+
end
|
82
|
+
|
83
|
+
def source_file (object)
|
84
|
+
object.source_file if object.respond_to?(:source_file)
|
85
|
+
end
|
86
|
+
|
87
|
+
def mark (type, object)
|
88
|
+
@analyzer.reports << Report.new(report_type,
|
89
|
+
source_file(object), source_line(object), message(object))
|
90
|
+
end
|
91
|
+
|
92
|
+
# analyzer class name stripped of last word
|
93
|
+
def report_type
|
94
|
+
(self.class.name.demodulize.underscore.split('_')[0...-1] * '_').to_sym
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
class RoleExplosionAnalyzer < GeneralRulesAnalyzer
|
99
|
+
SMALL_ROLE_RULES_COUNT = 3
|
100
|
+
SMALL_ROLES_RATIO = 0.2
|
101
|
+
|
102
|
+
def analyze_policy
|
103
|
+
small_roles.length > 1 and small_roles.length.to_f / roles.length.to_f > SMALL_ROLES_RATIO
|
104
|
+
end
|
105
|
+
|
106
|
+
def message (object)
|
107
|
+
"The ratio of small roles is quite high (> %.0f%%). Consider refactoring." % (SMALL_ROLES_RATIO * 100)
|
108
|
+
end
|
109
|
+
|
110
|
+
private
|
111
|
+
def small_roles
|
112
|
+
roles.select {|role| role.rules.length < SMALL_ROLE_RULES_COUNT }
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
class InheritingPrivilegesAnalyzer < GeneralRulesAnalyzer
|
117
|
+
def analyze_rule (rule)
|
118
|
+
rule.privileges.any? {|privilege| rule.privileges.intersects?(privilege.ancestors) }
|
119
|
+
end
|
120
|
+
|
121
|
+
def message (object)
|
122
|
+
"At least one privilege inherits from another in this rule."
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
class ProposedPrivilegeHierarchyAnalyzer < GeneralRulesAnalyzer
|
127
|
+
# TODO respect, consider contexts
|
128
|
+
def analyze_privilege (privilege)
|
129
|
+
privileges.find do |other_privilege|
|
130
|
+
other_privilege != privilege and
|
131
|
+
other_privilege.rules.all? {|rule| rule.privileges.include?(privilege)}
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
def message (privilege)
|
136
|
+
other_privilege = analyze_privilege(privilege)
|
137
|
+
"Privilege #{other_privilege.to_sym} is always used together with #{privilege.to_sym}. " +
|
138
|
+
"Consider to include #{other_privilege.to_sym} in #{privilege.to_sym}."
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
class GeneralAuthorizationProcessor < SexpProcessor
|
143
|
+
def initialize(analyzer)
|
144
|
+
super()
|
145
|
+
self.auto_shift_type = true
|
146
|
+
self.require_empty = false
|
147
|
+
self.strict = false
|
148
|
+
@analyzer = analyzer
|
149
|
+
end
|
150
|
+
|
151
|
+
def analyze (sexp_array)
|
152
|
+
process(sexp_array)
|
153
|
+
analyze_rules
|
154
|
+
end
|
155
|
+
|
156
|
+
def analyze_rules
|
157
|
+
# to be implemented by specific processor
|
158
|
+
end
|
159
|
+
|
160
|
+
def process_iter (exp)
|
161
|
+
s(:iter, process(exp.shift), process(exp.shift), process(exp.shift))
|
162
|
+
end
|
163
|
+
|
164
|
+
def process_arglist (exp)
|
165
|
+
s(exp.collect {|inner_exp| process(inner_exp).shift})
|
166
|
+
end
|
167
|
+
|
168
|
+
def process_hash (exp)
|
169
|
+
s(Hash[*exp.collect {|inner_exp| process(inner_exp).shift}])
|
170
|
+
end
|
171
|
+
|
172
|
+
def process_lit (exp)
|
173
|
+
s(exp.shift)
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
class MergeableRulesProcessor < GeneralAuthorizationProcessor
|
178
|
+
def analyze_rules
|
179
|
+
if @has_permission
|
180
|
+
#p @has_permission
|
181
|
+
permissions_by_context_and_rules = @has_permission.inject({}) do |memo, permission|
|
182
|
+
key = [permission[:context], permission[:rules]]
|
183
|
+
memo[key] ||= []
|
184
|
+
memo[key] << permission
|
185
|
+
memo
|
186
|
+
end
|
187
|
+
|
188
|
+
permissions_by_context_and_rules.each do |key, rules|
|
189
|
+
if rules.length > 1
|
190
|
+
rule_lines = rules.collect {|rule| rule[:line] }
|
191
|
+
rules.each do |rule|
|
192
|
+
@analyzer.reports << Report.new(:mergeable_rules, "", rule[:line],
|
193
|
+
"Similar rules already in line(s) " +
|
194
|
+
rule_lines.reject {|l| l == rule[:line] } * ", ")
|
195
|
+
end
|
196
|
+
end
|
197
|
+
end
|
198
|
+
end
|
199
|
+
end
|
200
|
+
|
201
|
+
def process_call (exp)
|
202
|
+
klass = exp.shift
|
203
|
+
name = exp.shift
|
204
|
+
case name
|
205
|
+
when :role
|
206
|
+
analyze_rules
|
207
|
+
@has_permission = []
|
208
|
+
s(:call, klass, name)
|
209
|
+
when :has_permission_on
|
210
|
+
arglist_line = exp[0].line
|
211
|
+
arglist = process(exp.shift).shift
|
212
|
+
context = arglist.shift
|
213
|
+
args_hash = arglist.shift
|
214
|
+
@has_permission << {
|
215
|
+
:context => context,
|
216
|
+
:rules => [],
|
217
|
+
:privilege => args_hash && args_hash[:to],
|
218
|
+
# a hack: call exp line seems to be wrong
|
219
|
+
:line => arglist_line
|
220
|
+
}
|
221
|
+
s(:call, klass, name)
|
222
|
+
when :to
|
223
|
+
@has_permission.last[:privilege] = process(exp.shift).shift if @has_permission
|
224
|
+
s(:call, klass, name)
|
225
|
+
when :if_attribute
|
226
|
+
rules = process(exp.shift).shift
|
227
|
+
rules.unshift :if_attribute
|
228
|
+
@has_permission.last[:rules] << rules if @has_permission
|
229
|
+
s(:call, klass, name)
|
230
|
+
when :if_permitted_to
|
231
|
+
rules = process(exp.shift).shift
|
232
|
+
rules.unshift :if_permitted_to
|
233
|
+
@has_permission.last[:rules] << rules if @has_permission
|
234
|
+
s(:call, klass, name)
|
235
|
+
else
|
236
|
+
s(:call, klass, name, process(exp.shift))
|
237
|
+
end
|
238
|
+
end
|
239
|
+
end
|
240
|
+
|
241
|
+
class Report
|
242
|
+
attr_reader :type, :filename, :line, :message
|
243
|
+
def initialize (type, filename, line, msg)
|
244
|
+
@type = type
|
245
|
+
@filename = filename
|
246
|
+
@line = line
|
247
|
+
@message = msg
|
248
|
+
end
|
249
|
+
end
|
250
|
+
end
|
251
|
+
end
|
252
|
+
end
|
@@ -0,0 +1,253 @@
|
|
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
|
+
# * Plan by tackling each condition separately
|
9
|
+
# * e.g. two users have a permission through the same role,
|
10
|
+
# one should lose that
|
11
|
+
# * Consider privilege hierarchy
|
12
|
+
# * Consider merging, splitting roles, role hierarchies
|
13
|
+
# * Add privilege to existing rules
|
14
|
+
# * Features
|
15
|
+
# * Show consequences from changes: which users are affected,
|
16
|
+
# show users in graph
|
17
|
+
# * restructure GUI layout: more room for analyzing suggestions
|
18
|
+
# * AI: planning: ADL-like, actions with preconditions and effects
|
19
|
+
# * Removing need of intention
|
20
|
+
# * Evaluation of approaches with Analyzer algorithms
|
21
|
+
# * Consider constraints
|
22
|
+
#
|
23
|
+
# NOTE:
|
24
|
+
# * user.clone needs to clone role_symbols
|
25
|
+
# * user.role_symbols needs to respond to <<
|
26
|
+
# * user.login is needed
|
27
|
+
#
|
28
|
+
class ChangeAnalyzer < AbstractAnalyzer
|
29
|
+
|
30
|
+
def find_approaches_for (change_action, type, options, &tests)
|
31
|
+
raise ArgumentError, "Missing options" if !options[:on] or !options[:to]
|
32
|
+
|
33
|
+
# * strategy for removing: [remove privilege, add privilege to different role]
|
34
|
+
@seen_states = Set.new
|
35
|
+
# * heurisic: change of failed tests; small number of policy items
|
36
|
+
strategy = case [change_action, type]
|
37
|
+
when [:remove, :permission]
|
38
|
+
[:remove_role_from_user, :remove_privilege, :add_privilege,
|
39
|
+
:add_role, :assign_role_to_user]
|
40
|
+
when [:add, :permission]
|
41
|
+
[:add_role, :add_privilege, :assign_role_to_user]
|
42
|
+
else
|
43
|
+
raise ArgumentError, "Unknown change action/type: #{[change_action, type].inspect}"
|
44
|
+
end
|
45
|
+
|
46
|
+
candidates = []
|
47
|
+
viable_approaches = []
|
48
|
+
approach_checker = ApproachChecker.new(self, tests)
|
49
|
+
|
50
|
+
starting_candidate = Approach.new(@engine, options[:users], [])
|
51
|
+
if starting_candidate.check(approach_checker)
|
52
|
+
viable_approaches << starting_candidate
|
53
|
+
else
|
54
|
+
candidates << starting_candidate
|
55
|
+
end
|
56
|
+
|
57
|
+
step_count = 0
|
58
|
+
while !candidates.empty? and step_count < 100
|
59
|
+
next_step(viable_approaches, candidates, approach_checker, options[:to],
|
60
|
+
options[:on], strategy)
|
61
|
+
step_count += 1
|
62
|
+
end
|
63
|
+
|
64
|
+
# remove subsets
|
65
|
+
|
66
|
+
viable_approaches.sort!
|
67
|
+
end
|
68
|
+
|
69
|
+
class ApproachChecker
|
70
|
+
attr_reader :failed_test_count, :users
|
71
|
+
|
72
|
+
def initialize (analyzer, tests)
|
73
|
+
@analyzer, @tests = analyzer, tests
|
74
|
+
end
|
75
|
+
|
76
|
+
def check (engine, users)
|
77
|
+
@current_engine = engine
|
78
|
+
@failed_test_count = 0
|
79
|
+
@users = users
|
80
|
+
@ok = true
|
81
|
+
instance_eval(&@tests)
|
82
|
+
@ok
|
83
|
+
end
|
84
|
+
|
85
|
+
def assert (ok)
|
86
|
+
@failed_test_count += 1 unless ok
|
87
|
+
@ok &&= ok
|
88
|
+
end
|
89
|
+
|
90
|
+
def permit? (*args)
|
91
|
+
@current_engine.permit?(*args)
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
class Approach
|
96
|
+
attr_reader :steps, :engine, :users
|
97
|
+
def initialize (engine, users, steps)
|
98
|
+
@engine, @users, @steps = engine, users, steps
|
99
|
+
end
|
100
|
+
|
101
|
+
def check (approach_checker)
|
102
|
+
res = approach_checker.check(@engine, @users)
|
103
|
+
@failed_test_count = approach_checker.failed_test_count
|
104
|
+
#puts "CHECKING #{inspect} (#{res}, #{sort_value})"
|
105
|
+
res
|
106
|
+
end
|
107
|
+
|
108
|
+
def clone_for_step (*step_params)
|
109
|
+
self.class.new(@engine.clone, @users.clone, @steps + [Step.new(step_params)])
|
110
|
+
end
|
111
|
+
|
112
|
+
def changes
|
113
|
+
@steps.select {|step| step.length > 1}
|
114
|
+
end
|
115
|
+
|
116
|
+
def subset? (other_approach)
|
117
|
+
other_approach.changes.length >= changes.length &&
|
118
|
+
changes.all? {|step| other_approach.changes.any? {|step_2| step_2.eql?(step)} }
|
119
|
+
end
|
120
|
+
|
121
|
+
def state_hash
|
122
|
+
@engine.auth_rules.inject(0) do |memo, rule|
|
123
|
+
memo + rule.privileges.hash + rule.contexts.hash +
|
124
|
+
rule.attributes.hash + rule.role.hash
|
125
|
+
end +
|
126
|
+
@users.inject(0) {|memo, user| memo + user.role_symbols.hash } +
|
127
|
+
@engine.privileges.hash + @engine.privilege_hierarchy.hash +
|
128
|
+
@engine.roles.hash + @engine.role_hierarchy.hash
|
129
|
+
end
|
130
|
+
|
131
|
+
def sort_value
|
132
|
+
(changes.length + 1) + steps.length / 2 + (@failed_test_count.to_i + 1)
|
133
|
+
end
|
134
|
+
|
135
|
+
def inspect
|
136
|
+
"Approach (#{state_hash}): Steps: #{changes.map(&:inspect) * ', '}"# +
|
137
|
+
# "\n Roles: #{AnalyzerEngine.roles(@engine).map(&:to_sym).inspect}; " +
|
138
|
+
# "\n Users: #{@users.map(&:role_symbols).inspect}"
|
139
|
+
end
|
140
|
+
|
141
|
+
def <=> (other)
|
142
|
+
sort_value <=> other.sort_value
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
class Step < Array
|
147
|
+
def eql? (other)
|
148
|
+
# TODO use approach.users.index(self[idx]) ==
|
149
|
+
# other.approach.users.index(other[idx])
|
150
|
+
# instead of user.login
|
151
|
+
other.is_a?(Array) && other.length == length &&
|
152
|
+
(0...length).all? {|idx| self[idx].class == other[idx].class &&
|
153
|
+
((self[idx].respond_to?(:to_sym) && self[idx].to_sym == other[idx].to_sym) ||
|
154
|
+
(self[idx].respond_to?(:login) && self[idx].login == other[idx].login) ||
|
155
|
+
self[idx] == other[idx] ) }
|
156
|
+
end
|
157
|
+
|
158
|
+
def inspect
|
159
|
+
collect {|info| info.respond_to?(:to_sym) ? info.to_sym : (info.respond_to?(:login) ? info.login : info.class.name)}.inspect
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
protected
|
164
|
+
def next_step (viable_approaches, candidates, approach_checker,
|
165
|
+
privilege, context, strategy)
|
166
|
+
candidate = candidates.shift
|
167
|
+
next_in_strategy = strategy[candidate.steps.length % strategy.length]
|
168
|
+
|
169
|
+
#if @seen_states.include?([candidate.state_hash, next_in_strategy])
|
170
|
+
# puts "SKIPPING #{next_in_strategy}; #{candidate.inspect}"
|
171
|
+
#end
|
172
|
+
return if @seen_states.include?([candidate.state_hash, next_in_strategy])
|
173
|
+
@seen_states << [candidate.state_hash, next_in_strategy]
|
174
|
+
candidate.steps << [next_in_strategy]
|
175
|
+
candidates << candidate
|
176
|
+
|
177
|
+
new_approaches = []
|
178
|
+
|
179
|
+
#puts "#{next_in_strategy} on #{candidate.inspect}"
|
180
|
+
case next_in_strategy
|
181
|
+
when :add_role
|
182
|
+
# ensure non-existent name
|
183
|
+
approach = candidate.clone_for_step(:add_role, :new_role_for_change_analyzer)
|
184
|
+
if AnalyzerEngine.apply_change(approach.engine, approach.changes.last)
|
185
|
+
#AnalyzerEngine.apply_change(approach.engine, [:add_privilege, privilege, context, :new_role_for_change_analyzer])
|
186
|
+
new_approaches << approach
|
187
|
+
end
|
188
|
+
when :assign_role_to_user
|
189
|
+
candidate.users.each do |user|
|
190
|
+
relevant_roles(candidate).each do |role|
|
191
|
+
next if user.role_symbols.include?(role.to_sym)
|
192
|
+
approach = candidate.clone_for_step(:assign_role_to_user, role, user)
|
193
|
+
# beware of shallow copies!
|
194
|
+
cloned_user = user.clone
|
195
|
+
approach.users[approach.users.index(user)] = cloned_user
|
196
|
+
# possible on real user objects?
|
197
|
+
cloned_user.role_symbols << role.to_sym
|
198
|
+
new_approaches << approach
|
199
|
+
end
|
200
|
+
end
|
201
|
+
when :remove_role_from_user
|
202
|
+
candidate.users.each do |user|
|
203
|
+
user.role_symbols.each do |role_sym|
|
204
|
+
approach = candidate.clone_for_step(:remove_role_from_user, role_sym, user)
|
205
|
+
# beware of shallow copies!
|
206
|
+
cloned_user = user.clone
|
207
|
+
approach.users[approach.users.index(user)] = cloned_user
|
208
|
+
# possible on real user objects?
|
209
|
+
cloned_user.role_symbols.delete(role_sym)
|
210
|
+
new_approaches << approach
|
211
|
+
end
|
212
|
+
end
|
213
|
+
when :add_privilege
|
214
|
+
relevant_roles(candidate).each do |role|
|
215
|
+
approach = candidate.clone_for_step(:add_privilege, privilege, context, role)
|
216
|
+
AnalyzerEngine.apply_change(approach.engine, approach.changes.last)
|
217
|
+
new_approaches << approach
|
218
|
+
end
|
219
|
+
when :remove_privilege
|
220
|
+
relevant_roles(candidate).each do |role|
|
221
|
+
approach = candidate.clone_for_step(:remove_privilege, privilege, context, role)
|
222
|
+
if AnalyzerEngine.apply_change(approach.engine, approach.changes.last)
|
223
|
+
new_approaches << approach
|
224
|
+
end
|
225
|
+
end
|
226
|
+
else
|
227
|
+
raise "Unknown next strategy step #{next_in_strategy}"
|
228
|
+
end
|
229
|
+
|
230
|
+
new_approaches.each do |new_approach|
|
231
|
+
if new_approach.check(approach_checker)
|
232
|
+
unless viable_approaches.any? {|viable_approach| viable_approach.subset?(new_approach) }
|
233
|
+
#puts "New: #{new_approach.changes.inspect}\n #{viable_approaches.map(&:changes).inspect}"
|
234
|
+
viable_approaches.delete_if {|viable_approach| new_approach.subset?(viable_approach)}
|
235
|
+
viable_approaches << new_approach unless viable_approaches.find {|v_a| v_a.state_hash == new_approach.state_hash}
|
236
|
+
end
|
237
|
+
else
|
238
|
+
candidates << new_approach
|
239
|
+
end
|
240
|
+
end
|
241
|
+
|
242
|
+
candidates.sort!
|
243
|
+
end
|
244
|
+
|
245
|
+
def relevant_roles (approach)
|
246
|
+
#return AnalyzerEngine.roles(approach.engine)
|
247
|
+
(AnalyzerEngine.relevant_roles(approach.engine, approach.users) +
|
248
|
+
(approach.engine.roles.include?(:new_role_for_change_analyzer) ?
|
249
|
+
[AnalyzerEngine::Role.for_sym(:new_role_for_change_analyzer, approach.engine)] : [])).uniq
|
250
|
+
end
|
251
|
+
end
|
252
|
+
end
|
253
|
+
end
|