abide_dev_utils 0.9.7 → 0.10.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile.lock +24 -8
- data/abide_dev_utils.gemspec +1 -0
- data/lib/abide_dev_utils/cem.rb +72 -0
- data/lib/abide_dev_utils/cli/cem.rb +73 -0
- data/lib/abide_dev_utils/cli/xccdf.rb +12 -1
- data/lib/abide_dev_utils/cli.rb +2 -0
- data/lib/abide_dev_utils/files.rb +34 -0
- data/lib/abide_dev_utils/version.rb +1 -1
- data/lib/abide_dev_utils/xccdf/diff/benchmark/number_title.rb +270 -0
- data/lib/abide_dev_utils/xccdf/diff/benchmark/profile.rb +104 -0
- data/lib/abide_dev_utils/xccdf/diff/benchmark/property.rb +127 -0
- data/lib/abide_dev_utils/xccdf/diff/benchmark/property_existence.rb +47 -0
- data/lib/abide_dev_utils/xccdf/diff/benchmark.rb +267 -0
- data/lib/abide_dev_utils/xccdf/diff/utils.rb +30 -0
- data/lib/abide_dev_utils/xccdf/diff.rb +233 -0
- data/lib/abide_dev_utils/xccdf/parser/objects/digest_object.rb +118 -0
- data/lib/abide_dev_utils/xccdf/parser/objects/numbered_object.rb +104 -0
- data/lib/abide_dev_utils/xccdf/parser/objects.rb +741 -0
- data/lib/abide_dev_utils/xccdf/parser.rb +52 -0
- data/lib/abide_dev_utils/xccdf.rb +9 -122
- data/new_diff.rb +48 -0
- metadata +34 -6
@@ -0,0 +1,104 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'abide_dev_utils/xccdf/diff/benchmark/property_existence'
|
4
|
+
|
5
|
+
module AbideDevUtils
|
6
|
+
module XCCDF
|
7
|
+
module Diff
|
8
|
+
# Diffs two sets of XCCDF profiles.
|
9
|
+
class ProfileDiff
|
10
|
+
def initialize(profiles, other_profiles)
|
11
|
+
new_profile_rule_objs(profiles, other_profiles)
|
12
|
+
end
|
13
|
+
|
14
|
+
def diff_hash(diff_type, profile1, prof1_rules, profile2, prof2_rules)
|
15
|
+
{
|
16
|
+
|
17
|
+
}
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def new_profile_rule_objs(profiles, other_profiles)
|
23
|
+
profile_objs = containers_from_profile_list(profiles)
|
24
|
+
other_profile_objs = containers_from_profile_list(other_profiles)
|
25
|
+
@self_prop_checker = PropertyExistenceChecker.new(profile_objs, other_profile_objs)
|
26
|
+
@other_prop_checker = PropertyExistenceChecker.new(other_profile_objs, profile_objs)
|
27
|
+
profile_objs.map { |p| p.prop_checker = @self_prop_checker }
|
28
|
+
other_profile_objs.map { |p| p.prop_checker = @other_prop_checker }
|
29
|
+
@profile_rule_objs = profile_objs
|
30
|
+
@other_profile_rule_objs = other_profile_objs
|
31
|
+
end
|
32
|
+
|
33
|
+
def containers_from_profile_list(profile_list)
|
34
|
+
profile_list.each_with_object([]) do |profile, ary|
|
35
|
+
ary << ProfileRuleContainer.new(profile)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
# Checks property existence in both profiles.
|
41
|
+
class PropChecker < AbideDevUtils::XCCDF::Diff::Benchmark::PropertyExistence
|
42
|
+
def initialize(profile_rule_objs, other_profile_rule_objs)
|
43
|
+
super
|
44
|
+
@profile_rule_objs = profile_rule_objs
|
45
|
+
@other_profile_rule_objs = other_profile_rule_objs
|
46
|
+
@profiles = profile_rule_objs.map(&:profile)
|
47
|
+
@other_profiles = other_profile_rule_objs.map(&:profile)
|
48
|
+
end
|
49
|
+
|
50
|
+
def profile(profile)
|
51
|
+
profile_key = profile.respond_to?(:id) ? profile.id : profile
|
52
|
+
property_existence(profile_key, @profiles, @other_profiles)
|
53
|
+
end
|
54
|
+
|
55
|
+
def rule_in_profile(rule, profile, rule_key: :title)
|
56
|
+
rk = rule.respond_to?(rule_key) ? rule.send(rule_key) : rule
|
57
|
+
rules = @profiles.find { |p| p.id == profile }.linked_rule.map(&rk)
|
58
|
+
other_rules = @other_profiles.find { |p| p.id == profile }.linked_rule.map(&rk)
|
59
|
+
property_existence(rk, rules, other_rules)
|
60
|
+
end
|
61
|
+
|
62
|
+
def added_profiles
|
63
|
+
added(@other_profiles.map(&:id), @profiles.map(&:id))
|
64
|
+
end
|
65
|
+
|
66
|
+
def removed_profiles
|
67
|
+
removed(@profiles.map(&:id), @other_profiles.map(&:id))
|
68
|
+
end
|
69
|
+
|
70
|
+
def added_rules_by_profile
|
71
|
+
@rules_by_profile.each_with_object({}) do |(profile, rules), hsh|
|
72
|
+
next unless @other_rules_by_profile.key?(profile)
|
73
|
+
|
74
|
+
hsh[profile] = added(rules, @other_rules_by_profile[profile])
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def removed_rules_by_profile
|
79
|
+
@rules_by_profile.each_with_object({}) do |(profile, rules), hsh|
|
80
|
+
next unless @other_rules_by_profile.key?(profile)
|
81
|
+
|
82
|
+
hsh[profile] = removed(rules, @other_rules_by_profile[profile])
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
class ProfileRuleContainer
|
88
|
+
include ::Comparable
|
89
|
+
attr_accessor :prop_checker
|
90
|
+
attr_reader :profile, :rules
|
91
|
+
|
92
|
+
def initialize(profile, prop_checker = nil)
|
93
|
+
@profile = profile
|
94
|
+
@rules = profile.linked_rule
|
95
|
+
@prop_checker = prop_checker
|
96
|
+
end
|
97
|
+
|
98
|
+
def <=>(other)
|
99
|
+
@profile.id <=> other.profile.id
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
@@ -0,0 +1,127 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'amatch'
|
4
|
+
|
5
|
+
module AbideDevUtils
|
6
|
+
module XCCDF
|
7
|
+
module Diff
|
8
|
+
# Diffs benchmark properties.
|
9
|
+
module BenchmarkPropertyDiff
|
10
|
+
DEFAULT_PROPERTY_DIFF_OPTS = {
|
11
|
+
rule_properties_for_similarity: %i[title description rationale fixtext],
|
12
|
+
rule_properties_for_confidence: %i[description rationale fixtext],
|
13
|
+
rule_confidence_property_threshold: 0.7,
|
14
|
+
rule_confidence_total_threshold: 0.5,
|
15
|
+
digest_similarity_threshold: 0.75,
|
16
|
+
digest_similarity_label_weights: {
|
17
|
+
'title' => 4.0,
|
18
|
+
},
|
19
|
+
digest_similarity_only_labels: %w[title description fixtext rationale],
|
20
|
+
digest_top_x_similarities: 10,
|
21
|
+
}.freeze
|
22
|
+
|
23
|
+
def safe_rule_prop(rule, prop)
|
24
|
+
rule.respond_to?(prop) ? rule.send(prop).to_s : :none
|
25
|
+
end
|
26
|
+
|
27
|
+
def self_rule_vals
|
28
|
+
@self_rule_vals ||= {}
|
29
|
+
end
|
30
|
+
|
31
|
+
def other_rule_vals
|
32
|
+
@other_rule_vals ||= {}
|
33
|
+
end
|
34
|
+
|
35
|
+
def add_rule_val(rule, prop, val, container: nil)
|
36
|
+
raise ArgumentError, 'container must not be nil' if container.nil?
|
37
|
+
|
38
|
+
return unless container.dig(rule, prop).nil?
|
39
|
+
|
40
|
+
container[rule] ||= {}
|
41
|
+
container[rule][prop] = val
|
42
|
+
end
|
43
|
+
|
44
|
+
def add_self_rule_val(rule, prop, val)
|
45
|
+
add_rule_val(rule, prop, val, container: self_rule_vals)
|
46
|
+
end
|
47
|
+
|
48
|
+
def add_other_rule_val(rule, prop, val)
|
49
|
+
add_rule_val(rule, prop, val, container: other_rule_vals)
|
50
|
+
end
|
51
|
+
|
52
|
+
def same_rule?(prop_similarities)
|
53
|
+
confidence_indicator = 0.0
|
54
|
+
opts[:rule_properties_for_confidence].each do |prop|
|
55
|
+
confidence_indicator += 1.0 if prop_similarities[prop] >= opts[:rule_confidence_property_threshold]
|
56
|
+
end
|
57
|
+
(confidence_indicator / opts[:rule_properties_for_confidence].length) >= opts[:rule_confidence_total_threshold]
|
58
|
+
end
|
59
|
+
|
60
|
+
def maxed_digest_similarities(child, other_children)
|
61
|
+
similarities = other_children.each_with_object([]) do |other_child, ary|
|
62
|
+
if other_child.digest_equal? child
|
63
|
+
ary << [1.0, other_child]
|
64
|
+
next
|
65
|
+
end
|
66
|
+
|
67
|
+
d_sim = child.digest_similarity(other_child,
|
68
|
+
only_labels: opts[:digest_similarity_only_labels],
|
69
|
+
label_weights: opts[:digest_similarity_label_weights])
|
70
|
+
ary << [d_sim, other_child]
|
71
|
+
end
|
72
|
+
max_digest_similarities(similarities)
|
73
|
+
end
|
74
|
+
|
75
|
+
def max_digest_similarities(digest_similarities)
|
76
|
+
digest_similarities.reject! { |s| s[0] < opts[:digest_similarity_threshold] }
|
77
|
+
return digest_similarities if digest_similarities.empty?
|
78
|
+
|
79
|
+
digest_similarities.max_by(opts[:digest_top_x_similarities]) { |s| s[0] }
|
80
|
+
end
|
81
|
+
|
82
|
+
def rule_property_similarity(rule1, rule2)
|
83
|
+
prop_similarities = {}
|
84
|
+
prop_diff = {}
|
85
|
+
opts[:rule_properties_for_similarity].each do |prop|
|
86
|
+
add_self_rule_val(rule1, prop, safe_rule_prop(rule1, prop).to_s)
|
87
|
+
add_other_rule_val(rule2, prop, safe_rule_prop(rule2, prop).to_s)
|
88
|
+
prop_similarities[prop] = self_rule_vals[rule1][prop].levenshtein_similar(other_rule_vals[rule2][prop])
|
89
|
+
if prop_similarities[prop] < 1.0
|
90
|
+
prop_diff[prop] = { self: self_rule_vals[rule1][prop], other: other_rule_vals[rule2][prop] }
|
91
|
+
end
|
92
|
+
end
|
93
|
+
total = prop_similarities.values.sum / opts[:rule_properties_for_similarity].length
|
94
|
+
{
|
95
|
+
total: total,
|
96
|
+
prop_similarities: prop_similarities,
|
97
|
+
prop_diff: prop_diff,
|
98
|
+
confident_same: same_rule?(prop_similarities),
|
99
|
+
}
|
100
|
+
end
|
101
|
+
|
102
|
+
def most_similar(child, maxed_digest_similarities)
|
103
|
+
most_similar_map = maxed_digest_similarities.each_with_object({}) do |similarity, h|
|
104
|
+
prop_similarities = rule_property_similarity(child, similarity[1])
|
105
|
+
if child.title.to_s == similarity[1].title.to_s
|
106
|
+
prop_similarities[:total] = 99.0 # magic number denoting a title match
|
107
|
+
end
|
108
|
+
h[prop_similarities[:total]] = { self: child, other: similarity[1] }.merge(prop_similarities)
|
109
|
+
end
|
110
|
+
most_similar_map[most_similar_map.keys.max]
|
111
|
+
end
|
112
|
+
|
113
|
+
def find_most_similar(children, other_children)
|
114
|
+
children.each_with_object({}) do |benchmark_child, h|
|
115
|
+
maxed_similarities = maxed_digest_similarities(benchmark_child, other_children)
|
116
|
+
next if maxed_similarities.empty?
|
117
|
+
|
118
|
+
best = most_similar(benchmark_child, maxed_similarities)
|
119
|
+
next if best.nil? || best.empty?
|
120
|
+
|
121
|
+
h[benchmark_child] = best
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module AbideDevUtils
|
4
|
+
module XCCDF
|
5
|
+
module Diff
|
6
|
+
# PropertyExistenceChecker provides methods to check existence state of various properties
|
7
|
+
class PropertyExistenceChecker
|
8
|
+
def initialize(*_args); end
|
9
|
+
|
10
|
+
# Compares two arrays (or other iterables implementing `#to_a`)
|
11
|
+
# containing properies and returns an array of the properties
|
12
|
+
# that are added by other_props but not in self_props.
|
13
|
+
def added(self_props, other_props)
|
14
|
+
other_props.to_a - self_props.to_a
|
15
|
+
end
|
16
|
+
|
17
|
+
# Compares two arrays (or other iterables implementing `#to_a`)
|
18
|
+
# containing properies and returns an array of the properties
|
19
|
+
# that are removed by other_props but exist in self_props.
|
20
|
+
def removed(this, other)
|
21
|
+
this.to_a - other.to_a
|
22
|
+
end
|
23
|
+
|
24
|
+
# Returns a hash of existence states and their inverse.
|
25
|
+
def self.inverse_existence_state
|
26
|
+
{
|
27
|
+
removed: :added,
|
28
|
+
added: :removed,
|
29
|
+
exists: :exists,
|
30
|
+
}
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def property_existence(property, self_props, other_props)
|
36
|
+
if self_props.include?(property) && !other_props.include?(property)
|
37
|
+
:removed
|
38
|
+
elsif !self_props.include?(property) && other_props.include?(property)
|
39
|
+
:added
|
40
|
+
else
|
41
|
+
:exists
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,267 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'abide_dev_utils/xccdf/diff/benchmark/number_title'
|
4
|
+
require 'abide_dev_utils/xccdf/diff/benchmark/property'
|
5
|
+
require 'abide_dev_utils/xccdf/diff/utils'
|
6
|
+
require 'abide_dev_utils/xccdf/parser'
|
7
|
+
|
8
|
+
module AbideDevUtils
|
9
|
+
module XCCDF
|
10
|
+
# Holds methods and classes used to diff XCCDF-derived objects.
|
11
|
+
module Diff
|
12
|
+
# Class for benchmark diffs
|
13
|
+
class BenchmarkDiff
|
14
|
+
include AbideDevUtils::XCCDF::Diff::BenchmarkPropertyDiff
|
15
|
+
attr_reader :self, :other, :opts
|
16
|
+
|
17
|
+
DEFAULT_OPTS = {
|
18
|
+
only_classes: %w[rule],
|
19
|
+
}.freeze
|
20
|
+
|
21
|
+
# Used for filtering by level and profile
|
22
|
+
LVL_PROF_DEFAULT = [nil, nil].freeze
|
23
|
+
|
24
|
+
# @param xml1 [String] path to the first benchmark XCCDF xml file
|
25
|
+
# @param xml2 [String] path to the second benchmark XCCDF xml file
|
26
|
+
# @param opts [Hash] options hash
|
27
|
+
def initialize(xml1, xml2, opts = {})
|
28
|
+
@self = new_benchmark(xml1)
|
29
|
+
@other = new_benchmark(xml2)
|
30
|
+
@opts = DEFAULT_OPTS.merge(DEFAULT_PROPERTY_DIFF_OPTS).merge(opts)
|
31
|
+
@levels = []
|
32
|
+
@profiles = []
|
33
|
+
end
|
34
|
+
|
35
|
+
def method_missing(method_name, *args, &block)
|
36
|
+
if opts.key?(method_name)
|
37
|
+
opts[method_name]
|
38
|
+
elsif @diff&.key?(method_name)
|
39
|
+
@diff[method_name]
|
40
|
+
else
|
41
|
+
super
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def respond_to_missing?(method_name, include_private = false)
|
46
|
+
opts.key?(method_name) || @diff&.key?(method_name) || super
|
47
|
+
end
|
48
|
+
|
49
|
+
# Memoized getter for all "numbered" children for the "self" benchmark based on optional filters in opts
|
50
|
+
def self_numbered_children
|
51
|
+
@self_numbered_children ||= find_all_numbered_children(@self,
|
52
|
+
only_classes: opts[:only_classes],
|
53
|
+
level: opts[:level],
|
54
|
+
profile: opts[:profile])
|
55
|
+
end
|
56
|
+
|
57
|
+
# Memoized getter for all "numbered" children for the "other" benchmark based on optional filters in opts
|
58
|
+
def other_numbered_children
|
59
|
+
@other_numbered_children ||= find_all_numbered_children(@other,
|
60
|
+
only_classes: opts[:only_classes],
|
61
|
+
level: opts[:level],
|
62
|
+
profile: opts[:profile])
|
63
|
+
end
|
64
|
+
|
65
|
+
# Basic title diff
|
66
|
+
def numbered_children_title_diff
|
67
|
+
{
|
68
|
+
self: self_numbered_children.map { |c| c.title.to_s } - other_numbered_children.map { |c| c.title.to_s },
|
69
|
+
other: other_numbered_children.map { |c| c.title.to_s } - self_numbered_children.map { |c| c.title.to_s },
|
70
|
+
}
|
71
|
+
end
|
72
|
+
|
73
|
+
# Returns the output of a NumberTitleDiff object's diff function based on self_numbered_children and other_numbered_children
|
74
|
+
def number_title_diff
|
75
|
+
NumberTitleDiff.new(self_numbered_children, other_numbered_children).diff
|
76
|
+
end
|
77
|
+
|
78
|
+
# Hash of data about the "self" benchmark and the diff parameters
|
79
|
+
def from_benchmark
|
80
|
+
@from_benchmark ||= from_to_hash(@self)
|
81
|
+
end
|
82
|
+
|
83
|
+
# Hash of data about the "other" benchmark and the diff parameters
|
84
|
+
def to_benchmark
|
85
|
+
@to_benchmark ||= from_to_hash(@other)
|
86
|
+
end
|
87
|
+
|
88
|
+
# All levels that numbered children have been filtered on
|
89
|
+
def levels
|
90
|
+
@levels.flatten.uniq.empty? ? [:all] : @levels.flatten.uniq
|
91
|
+
end
|
92
|
+
|
93
|
+
# All profiles that numbered children have been filtered on
|
94
|
+
def profiles
|
95
|
+
@profiles.flatten.uniq.empty? ? [:all] : @profiles.flatten.uniq
|
96
|
+
end
|
97
|
+
|
98
|
+
# Returns a diff of the changes from the "self" xml (xml1) to the "other" xml (xml2)
|
99
|
+
# This function is memoized because the diff operation is expensive. To run the diff
|
100
|
+
# operation again, set the `new` parameter to `true`
|
101
|
+
# @param new [Boolean] Set to `true` to force a new diff operation
|
102
|
+
# return [Hash] the diff in hash format
|
103
|
+
def diff(new: false)
|
104
|
+
return @diff if @diff && !new
|
105
|
+
|
106
|
+
@diff = {}
|
107
|
+
@diff[:number_title] = number_title_diff
|
108
|
+
{ from: from_benchmark, to: to_benchmark, diff: @diff }
|
109
|
+
end
|
110
|
+
|
111
|
+
private
|
112
|
+
|
113
|
+
# Returns a Benchmark object from a XCCDF xml file path
|
114
|
+
# @param xml [String] path to a XCCDF xml file
|
115
|
+
def new_benchmark(xml)
|
116
|
+
AbideDevUtils::XCCDF::Parser.parse(xml)
|
117
|
+
end
|
118
|
+
|
119
|
+
# Returns a hash of benchmark data
|
120
|
+
# @param obj [AbideDevUtils::XCCDF::Parser::Objects::Benchmark]
|
121
|
+
# @return [Hash] diff-relevant benchmark information
|
122
|
+
def from_to_hash(obj)
|
123
|
+
{
|
124
|
+
title: obj.title.to_s,
|
125
|
+
version: obj.version.to_s,
|
126
|
+
compared: {
|
127
|
+
levels: levels,
|
128
|
+
profiles: profiles,
|
129
|
+
}
|
130
|
+
}
|
131
|
+
end
|
132
|
+
|
133
|
+
# Function to check if a numbered child meets inclusion criteria based on filtering
|
134
|
+
# options.
|
135
|
+
# @param child [Object] XCCDF parser object that includes AbideDevUtils::XCCDF::Parser::Objects::NumberedObject.
|
136
|
+
# @param only_classes [Array] class names as strings. When this is specified, only objects with those class names will be considered.
|
137
|
+
# @param level [String] Specifies the benchmark profile level to filter children on. Only applies to Rules linked to Profiles that have levels.
|
138
|
+
# @param profile [String] Specifies the benchmark profile to filter children on. Only applies to Rules that have linked Profiles.
|
139
|
+
# @return [TrueClass] if child meets all filtering criteria and should be included in the set.
|
140
|
+
# @return [FalseClass] if child does not meet all criteria and should be excluded from the set.
|
141
|
+
def include_numbered_child?(child, only_classes: [], level: nil, profile: nil)
|
142
|
+
return false unless valid_class?(child, only_classes)
|
143
|
+
return true if level.nil? && profile.nil?
|
144
|
+
|
145
|
+
validated_props = valid_profile_and_level(child, level, profile)
|
146
|
+
should_include = validated_props.none?(&:nil?)
|
147
|
+
new_validated_props_vars(validated_props) if should_include
|
148
|
+
should_include
|
149
|
+
end
|
150
|
+
|
151
|
+
# Adds level and profile to respective instance vars
|
152
|
+
# @param validated_props [Array] two item array: first item - profile level, second item - profile title
|
153
|
+
def new_validated_props_vars(validated_props)
|
154
|
+
@levels << validated_props[0]
|
155
|
+
@profiles << validated_props[1]
|
156
|
+
end
|
157
|
+
|
158
|
+
# Checks if the child's class is in the only_classes list, if applicable
|
159
|
+
# @param child [Object] the child whose class will be checked
|
160
|
+
# @param only_classes [Array] an array of class names as strings
|
161
|
+
# @return [TrueClass] if only_classes is empty or if child's class is in only_classes
|
162
|
+
# @return [FalseClass] if only_classes is not empty and child's class is not in only_classes
|
163
|
+
def valid_class?(child, only_classes = [])
|
164
|
+
only_classes.empty? || only_classes.include?(child.label)
|
165
|
+
end
|
166
|
+
|
167
|
+
# Returns a two-item array of a valid level and a valid profile
|
168
|
+
# @param child [Object] XCCDF parser object or array of XCCDF parser objects
|
169
|
+
# @param level [String] a profile level
|
170
|
+
# @param profile [String] a partial / full profile title
|
171
|
+
# @return [Array] two-item array: first item - Profile level or nil, second item - Profile title or nil
|
172
|
+
def valid_profile_and_level(child, level, profile)
|
173
|
+
return LVL_PROF_DEFAULT unless child.respond_to?(:linked_profile)
|
174
|
+
|
175
|
+
validate_profile_obj(child.linked_profile, level, profile)
|
176
|
+
end
|
177
|
+
|
178
|
+
# Returns array (or array of arrays) of valid level and valid profile based on child's linked profiles
|
179
|
+
# @param obj [Object] AbideDevUtils::XCCDF::Parser::Objects::Profile objects or array of them
|
180
|
+
# @param level [String] a profile level
|
181
|
+
# @param profile [String] a partial / full profile title
|
182
|
+
# @return [Array] two-item array: first item - Profile level or nil, second item - Profile title or nil
|
183
|
+
# @return [Array] Array of two item arrays if `obj` is an array of profiles
|
184
|
+
def validate_profile_obj(obj, level, profile)
|
185
|
+
return LVL_PROF_DEFAULT if obj.nil?
|
186
|
+
return validate_profile_objs(obj, level, profile) if obj.respond_to?(:each)
|
187
|
+
|
188
|
+
validated_level = valid_level?(obj, level) ? obj.level : nil
|
189
|
+
validated_profile = valid_profile?(obj, profile) ? obj.title.to_s : nil
|
190
|
+
[validated_level, validated_profile]
|
191
|
+
end
|
192
|
+
|
193
|
+
# Returns array of arrays of valid levels and valid profiles based on all children's linked profiles
|
194
|
+
# @param objs [Array] Array of AbideDevUtils::XCCDF::Parser::Objects::Profile objects
|
195
|
+
# @param level [String] a profile level
|
196
|
+
# @param profile [String] a partial / full profile title
|
197
|
+
# @return [Array] Array of two-item arrays
|
198
|
+
def validate_profile_objs(objs, level, profile)
|
199
|
+
found = [LVL_PROF_DEFAULT]
|
200
|
+
objs.each do |obj|
|
201
|
+
validated = validate_profile_obj(obj, level, profile)
|
202
|
+
next if validated.any?(&:nil?)
|
203
|
+
|
204
|
+
found << validated
|
205
|
+
end
|
206
|
+
found
|
207
|
+
end
|
208
|
+
|
209
|
+
# Checks if a given object has a matching level. This is done
|
210
|
+
# via a basic regex match on the object's #level method
|
211
|
+
# @param obj [AbideDevUtils::XCCDF::Parser::Objects::Profile] the profile object
|
212
|
+
# @param level [String] a level to check
|
213
|
+
# @return [TrueClass] if level matches
|
214
|
+
# @return [FalseClass] if level does not match
|
215
|
+
def valid_level?(obj, level)
|
216
|
+
return true if level.nil? || obj.level.nil?
|
217
|
+
|
218
|
+
obj.level.match?(/#{Regexp.escape level}/i)
|
219
|
+
end
|
220
|
+
|
221
|
+
# Checks if a given profile object has a matching title or id. This
|
222
|
+
# is done with a regex match against the profile's title as a string
|
223
|
+
# or against it's object string representation (the ID).
|
224
|
+
# @param obj [AbideDevUtils::XCCDF::Parser::Objects::Profile] the profile object
|
225
|
+
# @param profile [String] a profile string to check
|
226
|
+
# @return [TrueClass] if `profile` matches either the profile's title or ID
|
227
|
+
# @return [FalseClass] if `profile` matches neither
|
228
|
+
def valid_profile?(obj, profile)
|
229
|
+
return true if profile.nil?
|
230
|
+
|
231
|
+
obj.title.to_s.match?(/#{Regexp.escape profile}/i) ||
|
232
|
+
obj.to_s.match?(/#{Regexp.escape profile}/i)
|
233
|
+
end
|
234
|
+
|
235
|
+
# Finds all children of the benchmark that implement AbideDevUtils::XCCDF::Parser::Objects::NumberedObject
|
236
|
+
# that are not filtered out based on optional parameters. This method recursively walks down the hierarchy
|
237
|
+
# of the benchmark to ensure that all deeply nested objects are accounted for.
|
238
|
+
# @param benchmark [AbideDevUtils::XCCDF::Parser::Objects::Benchmark] the benchmark to check
|
239
|
+
# @param only_classes [Array] An array of class names. Only children with the specified class names will be returned
|
240
|
+
# @param level [String] A profile level. Only children that have linked profiles that match this level will be returned
|
241
|
+
# @param profile [String] A profile title / id. Only children that have linked profiles that match this title / id will be returned
|
242
|
+
# @param numbered_children [Array] An array of numbered children to check. If this is empty, we start checking at the top-level of
|
243
|
+
# the benchmark. To get all of the benchmark's numbered children, this should be an empty array when calling this method.
|
244
|
+
# @return [Array] A sorted array of numbered children.
|
245
|
+
def find_all_numbered_children(benchmark, only_classes: [], level: nil, profile: nil, numbered_children: [])
|
246
|
+
benchmark.find_children_that_respond_to(:number).each do |child|
|
247
|
+
numbered_children << child if include_numbered_child?(child,
|
248
|
+
only_classes: only_classes,
|
249
|
+
level: level,
|
250
|
+
profile: profile)
|
251
|
+
find_all_numbered_children(child,
|
252
|
+
only_classes: only_classes,
|
253
|
+
level: level,
|
254
|
+
profile: profile,
|
255
|
+
numbered_children: numbered_children)
|
256
|
+
end
|
257
|
+
numbered_children.sort
|
258
|
+
end
|
259
|
+
|
260
|
+
# Returns a subset of benchmark children based on a property of that child and search values
|
261
|
+
def find_subset_of_children(children, property, search_values)
|
262
|
+
children.select { |c| search_values.include?(c.send(property)) }
|
263
|
+
end
|
264
|
+
end
|
265
|
+
end
|
266
|
+
end
|
267
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module AbideDevUtils
|
4
|
+
module XCCDF
|
5
|
+
module Diff
|
6
|
+
# Holds the result of a diff on a per-item basis.
|
7
|
+
class DiffChangeResult
|
8
|
+
attr_reader :type, :old_value, :new_value
|
9
|
+
|
10
|
+
def initialize(type, old_value, new_value)
|
11
|
+
@type = type
|
12
|
+
@old_value = old_value
|
13
|
+
@new_value = new_value
|
14
|
+
end
|
15
|
+
|
16
|
+
def to_h
|
17
|
+
{ type: type, old_value: old_value, new_value: new_value }
|
18
|
+
end
|
19
|
+
|
20
|
+
def to_a
|
21
|
+
[type, old_value, new_value]
|
22
|
+
end
|
23
|
+
|
24
|
+
def to_s
|
25
|
+
"#{type}: #{old_value} -> #{new_value}"
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|