vrt 0.1

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.
@@ -0,0 +1,3 @@
1
+ # Pre-warm the in-memory store of these class instance vars when we launch the
2
+ # server. Prevents unnecessary file I/O per-request.
3
+ VRT.reload!
@@ -0,0 +1,12 @@
1
+ require 'rails/generators/base'
2
+
3
+ module Vrt
4
+ module Generators
5
+ class InstallGenerator < Rails::Generators::Base
6
+ source_root(File.expand_path(File.dirname(__FILE__)))
7
+ def create_initializer_file
8
+ copy_file '../vrt.rb', 'config/initializers/vrt.rb'
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,123 @@
1
+ # The 'guts' of the VRT library. Probably in need of refactoring in future.
2
+ # Will do file I/O the first time it's accessed, and will thereafter hold
3
+ # VRT versions in-memory.
4
+
5
+ require 'vrt/map'
6
+ require 'vrt/node'
7
+ require 'vrt/cross_version_mapping'
8
+
9
+ require 'date'
10
+ require 'json'
11
+ require 'pathname'
12
+
13
+ module VRT
14
+ DIR = Pathname.new(__dir__).join('data')
15
+ OTHER_OPTION = { 'id' => 'other',
16
+ 'name' => 'Other',
17
+ 'priority' => nil,
18
+ 'type' => 'category' }.freeze
19
+
20
+ @version_json = {}
21
+ @last_update = {}
22
+
23
+ module_function
24
+
25
+ extend CrossVersionMapping
26
+
27
+ # Infer the available versions of the VRT from the names of the files
28
+ # in the repo.
29
+ # The returned list is in semver order with the current version first.
30
+ def versions
31
+ @versions ||= json_dir_names.sort_by { |v| Gem::Version.new(v) }.reverse!
32
+ end
33
+
34
+ # Get the most recent version of the VRT.
35
+ def current_version
36
+ versions.first
37
+ end
38
+
39
+ def current_version?(version)
40
+ version == current_version
41
+ end
42
+
43
+ # Get the last updated timestamp of the VRT data (not schema!)
44
+ # Passing nil for version will return the latest version.
45
+ def last_updated(version = nil)
46
+ version ||= current_version
47
+ return @last_update[version] if @last_update[version]
48
+ metadata = JSON.parse(json_pathname(version).read)['metadata']
49
+ @last_update[version] = Date.parse(metadata['release_date'])
50
+ end
51
+
52
+ def current_categories
53
+ Map.new(current_version).categories
54
+ end
55
+
56
+ # Get all deprecated ids that would match in the given categories from the current version
57
+ def all_matching_categories(categories)
58
+ cross_version_category_mapping.select { |key, _value| categories.include?(key) }.values.flatten
59
+ end
60
+
61
+ # Finds the best match valid node. First looks at valid nodes in the given new version or finds
62
+ # the appropriate deprecated mapping. If neither is found it will walk up the tree to find a
63
+ # valid parent node before giving up and returning nil.
64
+ #
65
+ # @param [String] A valid vrt_id
66
+ # @param [String] (Optional - recommended) A valid vrt_version that the vrt_id exists in
67
+ # @param [string] (Optional) The preferred new vrt_version to find a match in
68
+ # @param [String] (Optional) The maximum depth to match in
69
+ # @return [VRT::Node|Nil] A valid VRT::Node object or nil if no best match could be found
70
+ def find_node(vrt_id:, version: nil, preferred_version: nil, max_depth: 'variant')
71
+ new_version = preferred_version || current_version
72
+ if Map.new(new_version).valid?(vrt_id)
73
+ Map.new(new_version).find_node(vrt_id, max_depth: max_depth)
74
+ elsif deprecated_node?(vrt_id)
75
+ find_deprecated_node(vrt_id, preferred_version, max_depth)
76
+ else
77
+ return nil unless version
78
+ find_valid_parent_node(vrt_id, version, new_version, max_depth)
79
+ end
80
+ end
81
+
82
+ # Load the VRT from text files, and parse it as JSON.
83
+ # If other: true, we append the OTHER_OPTION hash at runtime (not cached)
84
+ def get_json(version: nil, other: true)
85
+ version ||= current_version
86
+ @version_json[version] ||= json_for_version(version)
87
+ other ? @version_json[version] + [OTHER_OPTION] : @version_json[version]
88
+ end
89
+
90
+ # Get names of directories matching lib/data/<major>-<minor>/
91
+ def json_dir_names
92
+ DIR.entries
93
+ .map(&:basename)
94
+ .map(&:to_s)
95
+ .select { |dirname| dirname =~ /^[0-9]+\.[0-9]/ }.sort
96
+ end
97
+
98
+ # Get the Pathname for a particular version
99
+ def json_pathname(version)
100
+ DIR.join(version, 'vulnerability-rating-taxonomy.json')
101
+ end
102
+
103
+ # Load and parse JSON for some VRT version
104
+ def json_for_version(version)
105
+ JSON.parse(json_pathname(version).read)['content']
106
+ end
107
+
108
+ # Cache the VRT contents in-memory, so we're not hitting File I/O multiple times per
109
+ # request that needs it.
110
+ def reload!
111
+ unload!
112
+ versions
113
+ get_json
114
+ last_updated
115
+ end
116
+
117
+ # We separate unload! out, as we need to call it in test environments.
118
+ def unload!
119
+ @versions = nil
120
+ @version_json = {}
121
+ @last_update = {}
122
+ end
123
+ end
@@ -0,0 +1,45 @@
1
+ module VRT
2
+ module CrossVersionMapping
3
+ # Maps new_category_id: deprecated_node_id
4
+ def cross_version_category_mapping
5
+ category_map = {}
6
+ deprecated_node_json.each do |key, value|
7
+ latest_version = value.keys.sort_by { |n| Gem::Version.new(n) }.last
8
+ id = value[latest_version].split('.')[0]
9
+ category_map[id] ? category_map[id] << key : category_map[id] = [key]
10
+ end
11
+ category_map
12
+ end
13
+
14
+ # Map shape: { deprecated_id: { version: new_mapped_id } }
15
+ def deprecated_node_json
16
+ filename = VRT::DIR.join(current_version, 'deprecated-node-mapping.json')
17
+ File.file?(filename) ? JSON.parse(File.read(filename)) : {}
18
+ end
19
+
20
+ def deprecated_node?(vrt_id)
21
+ deprecated_node_json[vrt_id]
22
+ end
23
+
24
+ def latest_version_for_deprecated_node(vrt_id)
25
+ deprecated_node_json[vrt_id].keys.sort_by { |n| Gem::Version.new(n) }.last
26
+ end
27
+
28
+ def find_deprecated_node(vrt_id, new_version = nil, max_depth = 'variant')
29
+ version = latest_version_for_deprecated_node(vrt_id)
30
+ node_id = deprecated_node_json[vrt_id][new_version] || deprecated_node_json[vrt_id][version]
31
+ VRT::Map.new(new_version).find_node(node_id, max_depth: max_depth)
32
+ end
33
+
34
+ def find_valid_parent_node(vrt_id, old_version, new_version, max_depth)
35
+ old_node = VRT::Map.new(old_version).find_node(vrt_id)
36
+ new_map = VRT::Map.new(new_version)
37
+ if new_map.valid?(vrt_id)
38
+ new_map.find_node(vrt_id, max_depth: max_depth)
39
+ else
40
+ return nil if old_node.parent.nil?
41
+ find_valid_parent_node(old_node.parent.qualified_vrt_id, old_version, new_version, max_depth)
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,82 @@
1
+ module VRT
2
+ class Map
3
+ DEPTH_MAP = {
4
+ 'category' => 1,
5
+ 'subcategory' => 2,
6
+ 'variant' => 3
7
+ }.freeze
8
+
9
+ attr_reader :structure, :version
10
+
11
+ def initialize(version = nil)
12
+ @version = version || VRT.current_version
13
+ @structure = build_structure
14
+ @found_nodes = {}
15
+ @lineages = {}
16
+ end
17
+
18
+ def find_node(string, max_depth: 'variant')
19
+ @found_nodes[string + max_depth] ||= walk_node_tree(string, max_depth: max_depth)
20
+ end
21
+
22
+ def valid?(node)
23
+ return false unless node =~ /[[:lower]]/
24
+ node == 'other' || find_node(node)
25
+ end
26
+
27
+ def get_lineage(string, max_depth: 'variant')
28
+ @lineages[string] ||= construct_lineage(string, max_depth)
29
+ end
30
+
31
+ # Returns list of top level categories in the shape:
32
+ # [{ value: category_id, label: category_name }]
33
+ def categories
34
+ structure.keys.map do |key|
35
+ node = find_node(key.to_s, max_depth: 'category')
36
+ { value: node.id, label: node.name }
37
+ end
38
+ end
39
+
40
+ private
41
+
42
+ def construct_lineage(string, max_depth)
43
+ lineage = ''
44
+ walk_node_tree(string, max_depth: max_depth) do |ids, node, level|
45
+ lineage += node.name
46
+ lineage += ' > ' unless level == ids.length
47
+ end
48
+ lineage
49
+ end
50
+
51
+ def walk_node_tree(string, max_depth: 'variant')
52
+ id_tokens = string.split('.').map(&:to_sym)
53
+ return nil if id_tokens.size > 3
54
+ ids = id_tokens.take(DEPTH_MAP[max_depth])
55
+ node = @structure[ids[0]]
56
+ ids.each_index do |idx|
57
+ level = idx + 1
58
+ yield(ids, node, level) if block_given?
59
+ node = search(ids, node, level)
60
+ end
61
+ node
62
+ end
63
+
64
+ def search(ids, node, level)
65
+ last_level = level.eql?(ids.length)
66
+ last_level ? node : node&.children&.fetch(ids[level], false)
67
+ end
68
+
69
+ def build_structure
70
+ VRT.get_json(version: @version).reduce({}, &method(:build_node))
71
+ end
72
+
73
+ def build_node(memo, vrt, parent = nil)
74
+ node = Node.new(vrt.merge('version' => @version, 'parent' => parent))
75
+ if node.children?
76
+ node.children = vrt['children'].reduce({}) { |m, v| build_node(m, v, node) }
77
+ end
78
+ memo[node.id] = node
79
+ memo
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,46 @@
1
+ module VRT
2
+ class Node
3
+ attr_reader :id, :name, :priority, :type, :version, :parent, :qualified_vrt_id
4
+ attr_accessor :children
5
+
6
+ def initialize(attributes = {})
7
+ @id = attributes['id'].to_sym
8
+ @name = attributes['name']
9
+ @priority = attributes['priority']
10
+ @type = attributes['type']
11
+ @has_children = attributes.key?('children')
12
+ @children = {}
13
+ @version = attributes['version']
14
+ @parent = attributes['parent']
15
+ @qualified_vrt_id = construct_vrt_id
16
+ end
17
+
18
+ def children?
19
+ @has_children
20
+ end
21
+
22
+ def construct_vrt_id
23
+ parent ? "#{parent.qualified_vrt_id}.#{id}" : id.to_s
24
+ end
25
+
26
+ # Since this object contains references to parent and children,
27
+ # as_json must be overridden to avoid unending recursion.
28
+ def as_json(options = nil)
29
+ json = {}
30
+ instance_variables.each do |attribute|
31
+ attr_name = attribute.to_s.tr('@', '')
32
+ json[attr_name] = case attr_name
33
+ when 'parent'
34
+ parent&.qualified_vrt_id
35
+ when 'children'
36
+ children.inject({}) do |c, (k, v)|
37
+ c[k] = v.nil? ? v : v.as_json(options)
38
+ end
39
+ else
40
+ instance_variable_get(attribute)
41
+ end
42
+ end
43
+ json
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,3 @@
1
+ module Vrt
2
+ VERSION = '0.1'.freeze
3
+ end
metadata ADDED
@@ -0,0 +1,116 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: vrt
3
+ version: !ruby/object:Gem::Version
4
+ version: '0.1'
5
+ platform: ruby
6
+ authors:
7
+ - Barnett Klane
8
+ - Max Schwenk
9
+ - Adam David
10
+ autorequire:
11
+ bindir: bin
12
+ cert_chain: []
13
+ date: 2017-07-21 00:00:00.000000000 Z
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: bundler
17
+ requirement: !ruby/object:Gem::Requirement
18
+ requirements:
19
+ - - "~>"
20
+ - !ruby/object:Gem::Version
21
+ version: '1.14'
22
+ type: :development
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ requirements:
26
+ - - "~>"
27
+ - !ruby/object:Gem::Version
28
+ version: '1.14'
29
+ - !ruby/object:Gem::Dependency
30
+ name: rake
31
+ requirement: !ruby/object:Gem::Requirement
32
+ requirements:
33
+ - - ">="
34
+ - !ruby/object:Gem::Version
35
+ version: '0'
36
+ type: :development
37
+ prerelease: false
38
+ version_requirements: !ruby/object:Gem::Requirement
39
+ requirements:
40
+ - - ">="
41
+ - !ruby/object:Gem::Version
42
+ version: '0'
43
+ - !ruby/object:Gem::Dependency
44
+ name: rspec
45
+ requirement: !ruby/object:Gem::Requirement
46
+ requirements:
47
+ - - ">="
48
+ - !ruby/object:Gem::Version
49
+ version: '0'
50
+ type: :development
51
+ prerelease: false
52
+ version_requirements: !ruby/object:Gem::Requirement
53
+ requirements:
54
+ - - ">="
55
+ - !ruby/object:Gem::Version
56
+ version: '0'
57
+ - !ruby/object:Gem::Dependency
58
+ name: rubocop
59
+ requirement: !ruby/object:Gem::Requirement
60
+ requirements:
61
+ - - "~>"
62
+ - !ruby/object:Gem::Version
63
+ version: '0.48'
64
+ type: :development
65
+ prerelease: false
66
+ version_requirements: !ruby/object:Gem::Requirement
67
+ requirements:
68
+ - - "~>"
69
+ - !ruby/object:Gem::Version
70
+ version: '0.48'
71
+ description:
72
+ email:
73
+ - barnett@bugcrowd.com
74
+ - max.schwenk@bugcrowd.com
75
+ - adam.david@bugcrowd.com
76
+ executables: []
77
+ extensions: []
78
+ extra_rdoc_files: []
79
+ files:
80
+ - lib/data/1.0/vrt.schema.json
81
+ - lib/data/1.0/vulnerability-rating-taxonomy.json
82
+ - lib/data/1.1/deprecated-node-mapping.json
83
+ - lib/data/1.1/vrt.schema.json
84
+ - lib/data/1.1/vulnerability-rating-taxonomy.json
85
+ - lib/generators/vrt.rb
86
+ - lib/generators/vrt/install_generator.rb
87
+ - lib/vrt.rb
88
+ - lib/vrt/cross_version_mapping.rb
89
+ - lib/vrt/map.rb
90
+ - lib/vrt/node.rb
91
+ - lib/vrt/version.rb
92
+ homepage: http://rubygems.org/gems/vrt
93
+ licenses:
94
+ - MIT
95
+ metadata: {}
96
+ post_install_message:
97
+ rdoc_options: []
98
+ require_paths:
99
+ - lib
100
+ required_ruby_version: !ruby/object:Gem::Requirement
101
+ requirements:
102
+ - - ">="
103
+ - !ruby/object:Gem::Version
104
+ version: '0'
105
+ required_rubygems_version: !ruby/object:Gem::Requirement
106
+ requirements:
107
+ - - ">="
108
+ - !ruby/object:Gem::Version
109
+ version: '0'
110
+ requirements: []
111
+ rubyforge_project:
112
+ rubygems_version: 2.6.12
113
+ signing_key:
114
+ specification_version: 4
115
+ summary: Ruby wrapper for Bugcrowd's Vulnerability Rating Taxonomy
116
+ test_files: []