myprecious 0.1.2 → 0.2.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/bin/myprecious +1 -1
- data/lib/myprecious.rb +124 -39
- data/lib/myprecious/cves.rb +239 -0
- data/lib/myprecious/python_packages.rb +16 -1
- data/lib/myprecious/ruby_gems.rb +24 -6
- metadata +22 -21
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d1d5adc30e0ccc2fb1c7a183402c7ac0015a9fbdde70e18267ad61d21bf4d790
|
4
|
+
data.tar.gz: '07394d3a1ff9a238220b9ae4d2be7328a9878ee08c47ec7179e9564af53e9e60'
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6f152193febe7482d2e9aeb1f3435b56c84f127a897cd0e3c3f8e35c5027a93d955bcb04b09c1e16f6d3a691bb92e85ff15047e31ba0b956daa5664ff6430287
|
7
|
+
data.tar.gz: ba2ea748b6a1c5f566f28375776639cb774449c885b11db306d02f709b7d6c86d7727acad771fd9eedaab610fa5be93914aa30b30607ada1d66691a9362c1f20
|
data/bin/myprecious
CHANGED
data/lib/myprecious.rb
CHANGED
@@ -13,6 +13,7 @@ module MyPrecious
|
|
13
13
|
ONE_DAY = 60 * 60 * 24
|
14
14
|
end
|
15
15
|
require 'myprecious/data_caches'
|
16
|
+
require 'myprecious/cves'
|
16
17
|
|
17
18
|
module MyPrecious
|
18
19
|
extend Rake::DSL
|
@@ -20,6 +21,10 @@ module MyPrecious
|
|
20
21
|
Program = Rake::ToolkitProgram
|
21
22
|
Program.title = "myprecious Dependecy Reporting Tool"
|
22
23
|
|
24
|
+
def self.tracing_errors?
|
25
|
+
!ENV['TRACE_ERRORS'].to_s.empty?
|
26
|
+
end
|
27
|
+
|
23
28
|
def self.common_program_args(parser, args)
|
24
29
|
parser.on(
|
25
30
|
'-o', '--out FILE',
|
@@ -34,11 +39,12 @@ module MyPrecious
|
|
34
39
|
fpath = Pathname(fpath)
|
35
40
|
parser.invalid_args!("#{fpath} does not exist.") unless fpath.exist?
|
36
41
|
args.target = fpath
|
42
|
+
CVEs.config_dir = fpath
|
37
43
|
end
|
38
44
|
|
39
45
|
parser.on(
|
40
46
|
'--[no-]cache',
|
41
|
-
"Control caching of
|
47
|
+
"Control caching of dependency information"
|
42
48
|
) {|v| MyPrecious.caching_disabled = v}
|
43
49
|
end
|
44
50
|
|
@@ -273,13 +279,14 @@ module MyPrecious
|
|
273
279
|
def common_col_title(attr)
|
274
280
|
case attr
|
275
281
|
when :current_version then 'Our Version'
|
276
|
-
when :age then 'Age (
|
282
|
+
when :age then 'Age (days)'
|
277
283
|
when :latest_version then 'Latest Version'
|
278
284
|
when :latest_released then 'Date Available'
|
279
285
|
when :recommended_version then 'Recommended Version'
|
280
286
|
when :license then 'License Type'
|
281
287
|
when :changelog then 'Change Log'
|
282
288
|
when :obsolescence then 'How Bad'
|
289
|
+
when :cves then 'CVEs'
|
283
290
|
else
|
284
291
|
warn("'#{attr}' column does not have a mapped name")
|
285
292
|
attr
|
@@ -331,7 +338,7 @@ module MyPrecious
|
|
331
338
|
# to some extent like frozen Array instances.
|
332
339
|
#
|
333
340
|
class ColumnOrder
|
334
|
-
DEFAULT = %i[name current_version age latest_version latest_released recommended_version license changelog].freeze
|
341
|
+
DEFAULT = %i[name current_version age latest_version latest_released recommended_version cves license changelog].freeze
|
335
342
|
COLUMN_FROM_TEXT_NAME = {
|
336
343
|
'gem' => :name,
|
337
344
|
'package' => :name,
|
@@ -340,10 +347,11 @@ module MyPrecious
|
|
340
347
|
'how bad' => :obsolescence,
|
341
348
|
'latest version' => :latest_version,
|
342
349
|
'date available' => :latest_released,
|
343
|
-
'age (
|
350
|
+
'age (days)' => :age,
|
344
351
|
'license type' => :license,
|
345
352
|
/change ?log/ => :changelog,
|
346
353
|
'recommended version' => :recommended_version,
|
354
|
+
'cves' => :cves,
|
347
355
|
}
|
348
356
|
|
349
357
|
def initialize
|
@@ -422,80 +430,151 @@ module MyPrecious
|
|
422
430
|
# and returns enhanced Markdown for selected columns (e.g. +name+).
|
423
431
|
#
|
424
432
|
class MarkdownAdapter
|
433
|
+
QS_VALUE_UNSAFE = /#{URI::UNSAFE}|[&=]/
|
434
|
+
|
425
435
|
def initialize(dep)
|
426
436
|
super()
|
427
437
|
@dependency = dep
|
428
438
|
end
|
429
439
|
attr_reader :dependency
|
430
440
|
|
441
|
+
def self.embellish(attr_name, &blk)
|
442
|
+
define_method(attr_name) do
|
443
|
+
value = begin
|
444
|
+
dependency.send(attr_name)
|
445
|
+
rescue NoMethodError
|
446
|
+
raise
|
447
|
+
rescue StandardError
|
448
|
+
return "(error)"
|
449
|
+
end
|
450
|
+
|
451
|
+
begin
|
452
|
+
instance_exec(value, &blk)
|
453
|
+
rescue StandardError => ex
|
454
|
+
err_key = [ex.backtrace[0], ex.to_s]
|
455
|
+
unless (@errors ||= Set.new).include?(err_key)
|
456
|
+
@errors << err_key
|
457
|
+
if MyPrecious.tracing_errors?
|
458
|
+
$stderr.puts("Traceback (most recent call last):")
|
459
|
+
ex.backtrace[1..-1].reverse.each_with_index do |loc, i|
|
460
|
+
$stderr.puts("#{(i + 1).to_s.rjust(8)}: #{loc}")
|
461
|
+
end
|
462
|
+
$stderr.puts("#{ex.backtrace[0]}: #{ex} (#{ex.class} while computing #{attr_name})")
|
463
|
+
else
|
464
|
+
$stderr.puts("#{ex} (while computing #{attr_name})")
|
465
|
+
end
|
466
|
+
end
|
467
|
+
value
|
468
|
+
end
|
469
|
+
end
|
470
|
+
end
|
471
|
+
|
431
472
|
##
|
432
473
|
# Generate Markdown linking the +name+ to the homepage for the dependency
|
433
474
|
#
|
434
|
-
|
475
|
+
embellish(:name) do |base_val|
|
435
476
|
cswatch = begin
|
436
477
|
color_swatch + ' '
|
437
478
|
rescue StandardError
|
438
479
|
''
|
439
480
|
end
|
440
|
-
"#{cswatch}[#{
|
441
|
-
rescue
|
442
|
-
|
481
|
+
"#{cswatch}[#{base_val}](#{dependency.homepage_uri})"
|
482
|
+
rescue Gems::NotFound
|
483
|
+
base_val
|
484
|
+
end
|
485
|
+
|
486
|
+
##
|
487
|
+
# Decorate the latest version as a link to the release history
|
488
|
+
#
|
489
|
+
embellish(:latest_version) do |base_val|
|
490
|
+
release_history_url = begin
|
491
|
+
dependency.release_history_url
|
492
|
+
rescue StandardError
|
493
|
+
nil
|
494
|
+
end
|
495
|
+
|
496
|
+
if release_history_url
|
497
|
+
"[#{base_val}](#{release_history_url})"
|
498
|
+
else
|
499
|
+
base_val
|
500
|
+
end
|
443
501
|
end
|
444
502
|
|
445
503
|
##
|
446
504
|
# Include information about temporal difference between current and
|
447
505
|
# recommended versions
|
448
506
|
#
|
449
|
-
|
450
|
-
|
451
|
-
|
452
|
-
|
453
|
-
|
454
|
-
|
455
|
-
|
456
|
-
|
457
|
-
|
507
|
+
embellish(:recommended_version) do |recced_ver|
|
508
|
+
if recced_ver.kind_of?(String)
|
509
|
+
recced_ver = Gem::Version.new(recced_ver)
|
510
|
+
end
|
511
|
+
next recced_ver if dependency.current_version.nil? || dependency.current_version >= recced_ver
|
512
|
+
|
513
|
+
span_comment = begin
|
514
|
+
if days_newer = dependency.days_between_current_and_recommended
|
515
|
+
" -- #{days_newer} days newer"
|
516
|
+
else
|
517
|
+
""
|
458
518
|
end
|
459
|
-
"**#{recommended_version}**#{span_comment}"
|
460
|
-
else
|
461
|
-
recommended_version
|
462
519
|
end
|
463
|
-
|
464
|
-
|
520
|
+
|
521
|
+
cve_url = URI('https://nvd.nist.gov/products/cpe/search/results')
|
522
|
+
cve_url.query = URI.encode_www_form(
|
523
|
+
keyword: "cpe:2.3:a:*:#{dependency.name.downcase}:#{recced_ver}",
|
524
|
+
)
|
525
|
+
|
526
|
+
"**#{recced_ver}**#{span_comment} ([current CVEs](#{cve_url}))"
|
465
527
|
end
|
466
528
|
|
467
529
|
##
|
468
530
|
# Include update info in the license column
|
469
531
|
#
|
470
|
-
|
471
|
-
|
472
|
-
|
473
|
-
|
532
|
+
embellish(:license) do |base_val|
|
533
|
+
begin
|
534
|
+
update_info = base_val.update_info
|
535
|
+
rescue NoMethodError
|
536
|
+
base_val
|
474
537
|
else
|
475
|
-
|
538
|
+
update_info.to_s.empty? ? base_val : "#{base_val}<br/>(#{update_info})"
|
476
539
|
end
|
477
|
-
rescue StandardError
|
478
|
-
"(error)"
|
479
540
|
end
|
480
541
|
|
481
542
|
##
|
482
543
|
# Render short links for http: or https: changelog URLs
|
483
544
|
#
|
484
|
-
|
485
|
-
base_val = begin
|
486
|
-
dependency.changelog
|
487
|
-
rescue StandardError
|
488
|
-
return "(error)"
|
489
|
-
end
|
490
|
-
|
545
|
+
embellish(:changelog) do |base_val|
|
491
546
|
begin
|
492
547
|
uri = URI.parse(base_val)
|
493
548
|
if ['http', 'https'].include?(uri.scheme)
|
494
|
-
|
549
|
+
next "[on #{uri.hostname}](#{base_val})"
|
495
550
|
end
|
496
551
|
rescue StandardError
|
497
552
|
end
|
498
|
-
|
553
|
+
|
554
|
+
base_val
|
555
|
+
end
|
556
|
+
|
557
|
+
##
|
558
|
+
# Render links to NIST's NVD
|
559
|
+
#
|
560
|
+
NVD_CVE_URL_TEMPLATE = "https://nvd.nist.gov/vuln/detail/%s"
|
561
|
+
embellish(:cves) do |base_val|
|
562
|
+
base_val.map do |cve|
|
563
|
+
link_text_parts = [cve]
|
564
|
+
addnl_info_parts = []
|
565
|
+
begin
|
566
|
+
addnl_info_parts << cve.vendors.to_a.join(',') unless cve.vendors.empty?
|
567
|
+
rescue StandardError
|
568
|
+
end
|
569
|
+
begin
|
570
|
+
addnl_info_parts << cve.score.to_s if cve.score
|
571
|
+
rescue StandardError
|
572
|
+
end
|
573
|
+
unless addnl_info_parts.empty?
|
574
|
+
link_text_parts << "(#{addnl_info_parts.join(' - ')})"
|
575
|
+
end
|
576
|
+
"[#{link_text_parts.join(' ')}](#{NVD_CVE_URL_TEMPLATE % cve})"
|
577
|
+
end.join(', ')
|
499
578
|
end
|
500
579
|
|
501
580
|
def obsolescence
|
@@ -508,10 +587,16 @@ module MyPrecious
|
|
508
587
|
# Get a CSS-style hex color code corresponding to the obsolescence of the dependency
|
509
588
|
#
|
510
589
|
def color
|
590
|
+
red = "fb0e0e"
|
591
|
+
|
592
|
+
if (dependency.cves.map(&:score).compact.max || 0) >= 7
|
593
|
+
return red
|
594
|
+
end
|
595
|
+
|
511
596
|
case dependency.obsolescence
|
512
597
|
when :mild then "dde418"
|
513
598
|
when :moderate then "f9b733"
|
514
|
-
when :severe then
|
599
|
+
when :severe then red
|
515
600
|
else "4dda1b"
|
516
601
|
end
|
517
602
|
end
|
@@ -0,0 +1,239 @@
|
|
1
|
+
require 'date'
|
2
|
+
require 'digest'
|
3
|
+
require 'json'
|
4
|
+
require 'myprecious/data_caches'
|
5
|
+
require 'open3'
|
6
|
+
require 'pathname'
|
7
|
+
require 'rest-client'
|
8
|
+
require 'set'
|
9
|
+
|
10
|
+
module MyPrecious
|
11
|
+
module CVEs
|
12
|
+
extend DataCaching
|
13
|
+
|
14
|
+
MIN_GAP_SECONDS = 5
|
15
|
+
CONFIG_FILE = '.myprecious-cves.rb'
|
16
|
+
|
17
|
+
CVE_DATA_CACHE_DIR = MyPrecious.data_cache(DATA_DIR / "cve-data")
|
18
|
+
|
19
|
+
class <<self
|
20
|
+
attr_reader :config_dir
|
21
|
+
|
22
|
+
def config_dir=(val)
|
23
|
+
@config_dir = Pathname(val)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.last_query_time
|
28
|
+
@last_query_time ||= DateTime.now - 1
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.queried!
|
32
|
+
@last_query_time = DateTime.now
|
33
|
+
end
|
34
|
+
|
35
|
+
##
|
36
|
+
# If you don't specify version, you get to match against the applicable
|
37
|
+
# configurations on your own to determine which CVEs returned apply to
|
38
|
+
# the versions of the named package in which you are interested
|
39
|
+
#
|
40
|
+
def self.get_for(package_name, version='*')
|
41
|
+
nvd_url = URI("https://services.nvd.nist.gov/rest/json/cves/1.0")
|
42
|
+
nvd_url.query = URI.encode_www_form(
|
43
|
+
cpeMatchString: "cpe:2.3:a:*:#{package_name.downcase}:#{version}:*:*:*:*:*:*:*",
|
44
|
+
)
|
45
|
+
|
46
|
+
cache = CVE_DATA_CACHE_DIR / "#{Digest::SHA256.hexdigest(nvd_url.to_s)}.json"
|
47
|
+
cve_data = apply_cache(cache) do
|
48
|
+
# Use last_query_time to sleep if necessary
|
49
|
+
wait_time = MIN_GAP_SECONDS - (DateTime.now - last_query_time) * 24 * 3600
|
50
|
+
if wait_time > 0
|
51
|
+
sleep(wait_time)
|
52
|
+
end
|
53
|
+
|
54
|
+
response = RestClient.get(nvd_url.to_s)
|
55
|
+
queried!
|
56
|
+
|
57
|
+
JSON.parse(response.body)
|
58
|
+
end
|
59
|
+
|
60
|
+
begin
|
61
|
+
return cve_data['result']['CVE_Items'].map do |e|
|
62
|
+
applicability = objectify_configurations(package_name, e['configurations'])
|
63
|
+
score = (((e['impact'] || {})['baseMetricV3'] || {})['cvssV3'] || {})['baseScore']
|
64
|
+
cve = CVERecord.new(
|
65
|
+
e['cve']['CVE_data_meta']['ID'],
|
66
|
+
applicability.respond_to?(:vendors) ? applicability.vendors : nil,
|
67
|
+
score
|
68
|
+
)
|
69
|
+
|
70
|
+
[cve, applicability]
|
71
|
+
end.reject {|cve, a| a.respond_to?(:applies_to?) && !a.applies_to?(version)}
|
72
|
+
rescue StandardError => e
|
73
|
+
$stderr.puts "[WARN] #{e}\n\n#{JSON.dump(cve_data)}\n\n"
|
74
|
+
[]
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def self.config
|
79
|
+
if !@config && config_dir
|
80
|
+
if (config_path = config_dir / CONFIG_FILE).exist?
|
81
|
+
@config = begin
|
82
|
+
config_prog_output, status = Open3.capture2(RbConfig.ruby, config_path.to_s)
|
83
|
+
if status.success?
|
84
|
+
JSON.parse(config_prog_output)
|
85
|
+
else
|
86
|
+
$stderr.puts "#{config_path} did not exit cleanly (code ${status.exitstatus})"
|
87
|
+
{}
|
88
|
+
end
|
89
|
+
rescue StandardError
|
90
|
+
end
|
91
|
+
|
92
|
+
unless @config.kind_of?(Hash)
|
93
|
+
$stderr.puts "#{config_path} did not output a JSON configuration"
|
94
|
+
@config = {}
|
95
|
+
end
|
96
|
+
else
|
97
|
+
@config = {}
|
98
|
+
end
|
99
|
+
end
|
100
|
+
@config ||= {}
|
101
|
+
end
|
102
|
+
|
103
|
+
def self.objectify_configurations(package_name, configs)
|
104
|
+
if configs.kind_of?(Hash) && configs['CVE_data_version'] == "4.0"
|
105
|
+
Applicability_V4_0.new(package_name, configs)
|
106
|
+
else
|
107
|
+
configs
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
class CVERecord < String
|
112
|
+
def initialize(id, vendors, score)
|
113
|
+
super(id)
|
114
|
+
|
115
|
+
@vendors = vendors
|
116
|
+
@score = score
|
117
|
+
end
|
118
|
+
|
119
|
+
attr_accessor :vendors, :score
|
120
|
+
end
|
121
|
+
|
122
|
+
class Applicability_V4_0 < Hash
|
123
|
+
def initialize(package, configs)
|
124
|
+
super()
|
125
|
+
self.update(configs)
|
126
|
+
@package = package.downcase
|
127
|
+
end
|
128
|
+
attr_reader :package
|
129
|
+
|
130
|
+
def nodes
|
131
|
+
self['nodes']
|
132
|
+
end
|
133
|
+
|
134
|
+
def applies_to?(version)
|
135
|
+
package_nodes(nodes).any? do |node|
|
136
|
+
version_matches_node?(version, node)
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
def vendors
|
141
|
+
Set.new(each_vulnerable_cpe.map do |cpe|
|
142
|
+
cpe.split(':')[3]
|
143
|
+
end)
|
144
|
+
end
|
145
|
+
|
146
|
+
def package_nodes(node_list)
|
147
|
+
node_list.select do |node|
|
148
|
+
node['children'] || node['cpe_match'].any? do |pattern|
|
149
|
+
pattern['cpe23Uri'] =~ package_cpe_regexp
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
def each_vulnerable_cpe
|
155
|
+
return enum_for(:each_vulnerable_cpe) unless block_given?
|
156
|
+
|
157
|
+
remaining = nodes.to_a.dup
|
158
|
+
while (node = remaining.shift)
|
159
|
+
if node['children']
|
160
|
+
remaining.insert(0, *node['children'])
|
161
|
+
else
|
162
|
+
node['cpe_match'].each do |pattern|
|
163
|
+
next unless pattern['vulnerable']
|
164
|
+
cpe = pattern['cpe23Uri']
|
165
|
+
if package_cpe_regexp =~ cpe
|
166
|
+
yield cpe
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
def package_cpe_regexp
|
174
|
+
/^cpe:2.3:a:[^:]*:#{package}(:|$)/
|
175
|
+
end
|
176
|
+
|
177
|
+
def version_matches_node?(version, node)
|
178
|
+
test = (node['operator'] == 'AND') ? :all? : :any?
|
179
|
+
if node['children']
|
180
|
+
return node['children'].send(test) {|child| version_matches_node?(version, child)}
|
181
|
+
end
|
182
|
+
|
183
|
+
return node['cpe_match'].any? do |pattern|
|
184
|
+
cpe_entry_indicates_vulnerable_version?(version, pattern)
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
def cpe_entry_indicates_vulnerable_version?(version, pattern)
|
189
|
+
return false unless pattern['vulnerable']
|
190
|
+
|
191
|
+
cpe_vendor, cpe_product, cpe_version, cpe_update = pattern['cpe23Uri'].split(':')[3,4]
|
192
|
+
return false if (CVEs.config['blockedProducts'] ||= []).include?([cpe_vendor, cpe_product].join(':'))
|
193
|
+
return false if cpe_product != @package
|
194
|
+
if version == '*'
|
195
|
+
return true
|
196
|
+
end
|
197
|
+
return false unless [nil, '*', '-'].include?(cpe_update) # We'll ignore prerelease versions
|
198
|
+
if cpe_version != '*' && cpe_version == version
|
199
|
+
return true
|
200
|
+
end
|
201
|
+
|
202
|
+
if (range_start = pattern['versionStartIncluding'])
|
203
|
+
range_test = :<=
|
204
|
+
elsif (range_start = pattern['versionStartExcluding'])
|
205
|
+
range_test = :<
|
206
|
+
else
|
207
|
+
range_test = nil
|
208
|
+
end
|
209
|
+
if range_test && !version_compare(range_start, version).send(range_test, 0)
|
210
|
+
return false
|
211
|
+
end
|
212
|
+
|
213
|
+
if (range_end = pattern['versionEndIncluding'])
|
214
|
+
range_test = :<=
|
215
|
+
elsif (range_end = pattern['versionEndExcluding'])
|
216
|
+
range_test = :<
|
217
|
+
else
|
218
|
+
range_test = nil
|
219
|
+
end
|
220
|
+
if range_test && !version_compare(version, range_end).send(range_test, 0)
|
221
|
+
return false
|
222
|
+
end
|
223
|
+
|
224
|
+
return range_start || range_end
|
225
|
+
end
|
226
|
+
|
227
|
+
##
|
228
|
+
# Return a <=> b for version strings a and b
|
229
|
+
#
|
230
|
+
def version_compare(a, b)
|
231
|
+
make_comparable(a) <=> make_comparable(b)
|
232
|
+
end
|
233
|
+
|
234
|
+
def make_comparable(ver_str)
|
235
|
+
ver_str.split('.').map {|p| p.to_i}
|
236
|
+
end
|
237
|
+
end
|
238
|
+
end
|
239
|
+
end
|
@@ -1,5 +1,7 @@
|
|
1
|
+
require 'date'
|
1
2
|
require 'json'
|
2
3
|
require 'myprecious'
|
4
|
+
require 'myprecious/cves'
|
3
5
|
require 'myprecious/data_caches'
|
4
6
|
require 'open-uri'
|
5
7
|
require 'open3'
|
@@ -212,7 +214,7 @@ module MyPrecious
|
|
212
214
|
end
|
213
215
|
|
214
216
|
def latest_released
|
215
|
-
versions_with_release[0][1]
|
217
|
+
Date.parse(versions_with_release[0][1].to_s).to_s
|
216
218
|
end
|
217
219
|
|
218
220
|
##
|
@@ -260,12 +262,25 @@ module MyPrecious
|
|
260
262
|
LicenseDescription.new(get_package_info['info']['license'])
|
261
263
|
end
|
262
264
|
|
265
|
+
def cves
|
266
|
+
resolve_name!
|
267
|
+
resolve_version!
|
268
|
+
|
269
|
+
CVEs.get_for(name, current_version.to_s).map do |cve, applicability|
|
270
|
+
cve
|
271
|
+
end
|
272
|
+
end
|
273
|
+
|
263
274
|
def changelog
|
264
275
|
# This is wrong
|
265
276
|
info = get_package_info['info']
|
266
277
|
return info['project_url']
|
267
278
|
end
|
268
279
|
|
280
|
+
def release_history_url
|
281
|
+
"https://pypi.org/project/#{name}/#history"
|
282
|
+
end
|
283
|
+
|
269
284
|
def days_between_current_and_recommended
|
270
285
|
v, cv_rel = versions_with_release.find do |v, r|
|
271
286
|
case
|
data/lib/myprecious/ruby_gems.rb
CHANGED
@@ -1,4 +1,6 @@
|
|
1
|
+
require 'date'
|
1
2
|
require 'gems'
|
3
|
+
require 'myprecious/cves'
|
2
4
|
require 'myprecious/data_caches'
|
3
5
|
require 'pathname'
|
4
6
|
|
@@ -33,11 +35,11 @@ module MyPrecious
|
|
33
35
|
# used by the project. Each gem name will appear in only one +:current+
|
34
36
|
# iteration, but may occur in multiple +:reqs+ iterations.
|
35
37
|
#
|
36
|
-
def self.each_gem_used(fpath)
|
38
|
+
def self.each_gem_used(fpath, gemfile: 'Gemfile')
|
37
39
|
return enum_for(:each_gem_used, fpath) unless block_given?
|
38
40
|
|
39
|
-
gemlock = Pathname(fpath).join('
|
40
|
-
raise "No
|
41
|
+
gemlock = Pathname(fpath).join(gemfile + '.lock')
|
42
|
+
raise "No #{gemfile}.lock in #{fpath}" unless gemlock.exist?
|
41
43
|
|
42
44
|
section = nil
|
43
45
|
gemlock.each_line do |l|
|
@@ -64,9 +66,9 @@ module MyPrecious
|
|
64
66
|
# #current_version values and meaningful information in #version_reqs,
|
65
67
|
# as indicated in the Gemfile.lock for +fpath+.
|
66
68
|
#
|
67
|
-
def self.accum_gem_lock_info(fpath)
|
69
|
+
def self.accum_gem_lock_info(fpath, **opts)
|
68
70
|
{}.tap do |gems|
|
69
|
-
each_gem_used(fpath) do |entry_type, name, verreq|
|
71
|
+
each_gem_used(fpath, **opts) do |entry_type, name, verreq|
|
70
72
|
g = (gems[name] ||= RubyGemInfo.new(name))
|
71
73
|
|
72
74
|
case entry_type
|
@@ -156,7 +158,7 @@ module MyPrecious
|
|
156
158
|
|
157
159
|
def latest_released
|
158
160
|
return nil if versions_with_release.empty?
|
159
|
-
versions_with_release[0][1]
|
161
|
+
Date.parse(versions_with_release[0][1].to_s).to_s
|
160
162
|
end
|
161
163
|
|
162
164
|
##
|
@@ -202,6 +204,12 @@ module MyPrecious
|
|
202
204
|
end
|
203
205
|
end
|
204
206
|
|
207
|
+
def cves
|
208
|
+
CVEs.get_for(name, current_version && current_version.to_s).map do |cve, appl|
|
209
|
+
cve
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
205
213
|
def changelogs
|
206
214
|
gv_data = get_gems_versions.sort_by {|v| Gem::Version.new(v['number'])}.reverse
|
207
215
|
if current_version
|
@@ -214,6 +222,16 @@ module MyPrecious
|
|
214
222
|
changelogs[0]
|
215
223
|
end
|
216
224
|
|
225
|
+
def release_history_url
|
226
|
+
"https://rubygems.org/gems/#{name}/versions" if (
|
227
|
+
begin
|
228
|
+
get_gems_versions
|
229
|
+
rescue StandardError
|
230
|
+
nil
|
231
|
+
end
|
232
|
+
)
|
233
|
+
end
|
234
|
+
|
217
235
|
def days_between_current_and_recommended
|
218
236
|
v, cv_rel = versions_with_release.find {|v, r| v == current_version} || []
|
219
237
|
v, rv_rel = versions_with_release.find {|v, r| v == recommended_version} || []
|
metadata
CHANGED
@@ -1,55 +1,55 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: myprecious
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Balki Kodarapu
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-
|
11
|
+
date: 2020-12-17 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: gems
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
|
-
- - "~>"
|
18
|
-
- !ruby/object:Gem::Version
|
19
|
-
version: '1.0'
|
20
17
|
- - ">="
|
21
18
|
- !ruby/object:Gem::Version
|
22
19
|
version: 1.0.0
|
20
|
+
- - "~>"
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: '1.0'
|
23
23
|
type: :runtime
|
24
24
|
prerelease: false
|
25
25
|
version_requirements: !ruby/object:Gem::Requirement
|
26
26
|
requirements:
|
27
|
-
- - "~>"
|
28
|
-
- !ruby/object:Gem::Version
|
29
|
-
version: '1.0'
|
30
27
|
- - ">="
|
31
28
|
- !ruby/object:Gem::Version
|
32
29
|
version: 1.0.0
|
30
|
+
- - "~>"
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '1.0'
|
33
33
|
- !ruby/object:Gem::Dependency
|
34
34
|
name: git
|
35
35
|
requirement: !ruby/object:Gem::Requirement
|
36
36
|
requirements:
|
37
|
-
- - "~>"
|
38
|
-
- !ruby/object:Gem::Version
|
39
|
-
version: '1.5'
|
40
37
|
- - ">="
|
41
38
|
- !ruby/object:Gem::Version
|
42
39
|
version: 1.5.0
|
40
|
+
- - "~>"
|
41
|
+
- !ruby/object:Gem::Version
|
42
|
+
version: '1.5'
|
43
43
|
type: :runtime
|
44
44
|
prerelease: false
|
45
45
|
version_requirements: !ruby/object:Gem::Requirement
|
46
46
|
requirements:
|
47
|
-
- - "~>"
|
48
|
-
- !ruby/object:Gem::Version
|
49
|
-
version: '1.5'
|
50
47
|
- - ">="
|
51
48
|
- !ruby/object:Gem::Version
|
52
49
|
version: 1.5.0
|
50
|
+
- - "~>"
|
51
|
+
- !ruby/object:Gem::Version
|
52
|
+
version: '1.5'
|
53
53
|
- !ruby/object:Gem::Dependency
|
54
54
|
name: rake-toolkit_program
|
55
55
|
requirement: !ruby/object:Gem::Requirement
|
@@ -68,22 +68,22 @@ dependencies:
|
|
68
68
|
name: rest-client
|
69
69
|
requirement: !ruby/object:Gem::Requirement
|
70
70
|
requirements:
|
71
|
-
- - "~>"
|
72
|
-
- !ruby/object:Gem::Version
|
73
|
-
version: 2.0.2
|
74
71
|
- - ">="
|
75
72
|
- !ruby/object:Gem::Version
|
76
73
|
version: '2.0'
|
74
|
+
- - "~>"
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
version: 2.0.2
|
77
77
|
type: :runtime
|
78
78
|
prerelease: false
|
79
79
|
version_requirements: !ruby/object:Gem::Requirement
|
80
80
|
requirements:
|
81
|
-
- - "~>"
|
82
|
-
- !ruby/object:Gem::Version
|
83
|
-
version: 2.0.2
|
84
81
|
- - ">="
|
85
82
|
- !ruby/object:Gem::Version
|
86
83
|
version: '2.0'
|
84
|
+
- - "~>"
|
85
|
+
- !ruby/object:Gem::Version
|
86
|
+
version: 2.0.2
|
87
87
|
- !ruby/object:Gem::Dependency
|
88
88
|
name: parslet
|
89
89
|
requirement: !ruby/object:Gem::Requirement
|
@@ -178,6 +178,7 @@ extra_rdoc_files: []
|
|
178
178
|
files:
|
179
179
|
- bin/myprecious
|
180
180
|
- lib/myprecious.rb
|
181
|
+
- lib/myprecious/cves.rb
|
181
182
|
- lib/myprecious/data_caches.rb
|
182
183
|
- lib/myprecious/python_packages.rb
|
183
184
|
- lib/myprecious/ruby_gems.rb
|
@@ -200,7 +201,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
200
201
|
- !ruby/object:Gem::Version
|
201
202
|
version: '0'
|
202
203
|
requirements: []
|
203
|
-
rubygems_version: 3.
|
204
|
+
rubygems_version: 3.0.3
|
204
205
|
signing_key:
|
205
206
|
specification_version: 4
|
206
207
|
summary: Your precious dependencies!
|