vrt 0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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: []