abide_dev_utils 0.14.2 → 0.15.0
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.
- checksums.yaml +4 -4
- data/Gemfile.lock +19 -33
- data/abide_dev_utils.gemspec +0 -1
- data/lib/abide_dev_utils/cli/cem.rb +9 -8
- data/lib/abide_dev_utils/cli/jira.rb +48 -4
- data/lib/abide_dev_utils/cli/xccdf.rb +7 -13
- data/lib/abide_dev_utils/config.rb +3 -6
- data/lib/abide_dev_utils/files.rb +16 -6
- data/lib/abide_dev_utils/jira.rb +69 -1
- data/lib/abide_dev_utils/prompt.rb +3 -1
- data/lib/abide_dev_utils/version.rb +1 -1
- data/lib/abide_dev_utils/xccdf/diff.rb +64 -200
- data/lib/abide_dev_utils/xccdf/parser/helpers.rb +0 -93
- data/lib/abide_dev_utils/xccdf/parser/objects/diffable_object.rb +347 -0
- data/lib/abide_dev_utils/xccdf/parser/objects.rb +413 -89
- data/lib/abide_dev_utils/xccdf/parser.rb +8 -9
- data/lib/abide_dev_utils/xccdf.rb +1 -10
- metadata +4 -24
- data/lib/abide_dev_utils/xccdf/diff/benchmark/number_title.rb +0 -270
- data/lib/abide_dev_utils/xccdf/diff/benchmark/profile.rb +0 -104
- data/lib/abide_dev_utils/xccdf/diff/benchmark/property.rb +0 -127
- data/lib/abide_dev_utils/xccdf/diff/benchmark/property_existence.rb +0 -47
- data/lib/abide_dev_utils/xccdf/diff/benchmark.rb +0 -267
- data/lib/abide_dev_utils/xccdf/diff/utils.rb +0 -30
- data/lib/abide_dev_utils/xccdf/parser/objects/digest_object.rb +0 -118
@@ -1,267 +0,0 @@
|
|
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
|
@@ -1,30 +0,0 @@
|
|
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
|
@@ -1,118 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module AbideDevUtils
|
4
|
-
module XCCDF
|
5
|
-
module Parser
|
6
|
-
module Objects
|
7
|
-
# Methods for providing and comparing hash digests of objects
|
8
|
-
module DigestObject
|
9
|
-
# Excludes instance variables that are not used in the digest
|
10
|
-
def exclude_from_digest(exclude)
|
11
|
-
unless exclude.is_a?(Array) || exclude.is_a?(Symbol)
|
12
|
-
raise ArgumentError, 'exclude must be an Array or Symbol'
|
13
|
-
end
|
14
|
-
|
15
|
-
@exclude_from_digest ||= []
|
16
|
-
if exclude.is_a?(Array)
|
17
|
-
exclude.map! do |e|
|
18
|
-
normalize_exclusion(e)
|
19
|
-
end
|
20
|
-
@exclude_from_digest += exclude
|
21
|
-
else
|
22
|
-
@exclude_from_digest << normalize_exclusion(exclude)
|
23
|
-
end
|
24
|
-
@exclude_from_digest.uniq!
|
25
|
-
end
|
26
|
-
|
27
|
-
# Exclusions are instance variable symbols and must be prefixed with "@"
|
28
|
-
def normalize_exclusion(exclude)
|
29
|
-
exclude = "@#{exclude}" unless exclude.to_s.start_with?('@')
|
30
|
-
exclude.to_sym
|
31
|
-
end
|
32
|
-
|
33
|
-
# Checks SHA256 digest equality
|
34
|
-
def digest_equal?(other)
|
35
|
-
digest == other.digest
|
36
|
-
end
|
37
|
-
|
38
|
-
# Returns a SHA256 digest of the object, including the digests of all
|
39
|
-
# children
|
40
|
-
def digest
|
41
|
-
return @digest if defined?(@digest)
|
42
|
-
|
43
|
-
parts = [labeled_self_digest]
|
44
|
-
children.each { |child| parts << child.digest } unless children.empty?
|
45
|
-
@digest = parts.join('|')
|
46
|
-
@digest
|
47
|
-
end
|
48
|
-
|
49
|
-
# Returns a labeled digest of the current object
|
50
|
-
def labeled_self_digest
|
51
|
-
return "#{label}:#{Digest::SHA256.hexdigest(digestable_instance_variables)}" if respond_to?(:label)
|
52
|
-
|
53
|
-
"none:#{Digest::SHA256.hexdigest(digestable_instance_variables)}"
|
54
|
-
end
|
55
|
-
|
56
|
-
# Returns a string of all instance variable values that are not nil, empty, or excluded
|
57
|
-
def digestable_instance_variables
|
58
|
-
instance_vars = instance_variables.reject { |iv| @exclude_from_digest.include?(iv) }.sort_by!(&:to_s)
|
59
|
-
return 'empty' if instance_vars.empty?
|
60
|
-
|
61
|
-
var_vals = instance_vars.map { |iv| instance_variable_get(iv) }
|
62
|
-
var_vals.reject! { |v| v.nil? || v.empty? }
|
63
|
-
return 'empty' if var_vals.empty?
|
64
|
-
|
65
|
-
var_vals.join
|
66
|
-
end
|
67
|
-
|
68
|
-
# Compares two objects by their SHA256 digests
|
69
|
-
# and returns the degree to which they are similar
|
70
|
-
# as a percentage.
|
71
|
-
def digest_similarity(other, only_labels: [], label_weights: {})
|
72
|
-
digest_parts = sorted_digest_parts(digest)
|
73
|
-
number_compared = 0
|
74
|
-
cumulative_similarity = 0.0
|
75
|
-
digest_parts.each do |digest_part|
|
76
|
-
label, self_digest = split_labeled_digest(digest_part)
|
77
|
-
next unless only_labels.empty? || only_labels.include?(label)
|
78
|
-
|
79
|
-
label_weight = label_weights.key?(label) ? label_weights[label] : 1.0
|
80
|
-
sorted_digest_parts(other.digest).each do |other_digest_part|
|
81
|
-
other_label, other_digest = split_labeled_digest(other_digest_part)
|
82
|
-
next unless (label == other_label) && (self_digest == other_digest)
|
83
|
-
|
84
|
-
number_compared += 1
|
85
|
-
cumulative_similarity += 1.0 * label_weight
|
86
|
-
break # break when found
|
87
|
-
end
|
88
|
-
end
|
89
|
-
cumulative_similarity / (number_compared.zero? ? 1.0 : number_compared)
|
90
|
-
end
|
91
|
-
|
92
|
-
def sorted_digest_parts(dgst)
|
93
|
-
@sorted_digest_parts_cache = {} unless defined?(@sorted_digest_parts_cache)
|
94
|
-
return @sorted_digest_parts_cache[dgst] if @sorted_digest_parts_cache.key?(dgst)
|
95
|
-
|
96
|
-
@sorted_digest_parts_cache ||= {}
|
97
|
-
@sorted_digest_parts_cache[dgst] = dgst.split('|').sort_by { |part| split_labeled_digest(part).first }
|
98
|
-
@sorted_digest_parts_cache[dgst]
|
99
|
-
end
|
100
|
-
|
101
|
-
# If one of the digest parts is nil and the other is not, we can't compare
|
102
|
-
def non_compatible?(digest_part, other_digest_part)
|
103
|
-
(digest_part.nil? || other_digest_part.nil?) && digest_part != other_digest_part
|
104
|
-
end
|
105
|
-
|
106
|
-
# Splits a digest into a label and digest
|
107
|
-
def split_labeled_digest(digest_part)
|
108
|
-
@labeled_digest_part_cache = {} unless defined?(@labeled_digest_part_cache)
|
109
|
-
return @labeled_digest_part_cache[digest_part] if @labeled_digest_part_cache.key?(digest_part)
|
110
|
-
|
111
|
-
@labeled_digest_part_cache[digest_part] = digest_part.split(':')
|
112
|
-
@labeled_digest_part_cache[digest_part]
|
113
|
-
end
|
114
|
-
end
|
115
|
-
end
|
116
|
-
end
|
117
|
-
end
|
118
|
-
end
|