package-audit 0.3.0 → 0.4.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (56) hide show
  1. checksums.yaml +4 -4
  2. data/lib/package/audit/cli.rb +32 -32
  3. data/lib/package/audit/const/fields.rb +4 -4
  4. data/lib/package/audit/const/file.rb +1 -0
  5. data/lib/package/audit/const/yaml.rb +13 -0
  6. data/lib/package/audit/enum/option.rb +13 -0
  7. data/lib/package/audit/enum/report.rb +12 -0
  8. data/lib/package/audit/enum/technology.rb +14 -0
  9. data/lib/package/audit/formatter/risk.rb +1 -1
  10. data/lib/package/audit/formatter/version.rb +1 -1
  11. data/lib/package/audit/formatter/version_date.rb +1 -1
  12. data/lib/package/audit/formatter/vulnerability.rb +1 -1
  13. data/lib/package/audit/{package.rb → models/package.rb} +7 -6
  14. data/lib/package/audit/npm/node_collection.rb +21 -10
  15. data/lib/package/audit/npm/vulnerability_finder.rb +1 -1
  16. data/lib/package/audit/npm/yarn_lock_parser.rb +13 -2
  17. data/lib/package/audit/ruby/bundler_specs.rb +1 -1
  18. data/lib/package/audit/ruby/gem_collection.rb +23 -6
  19. data/lib/package/audit/ruby/gem_meta_data.rb +1 -1
  20. data/lib/package/audit/ruby/vulnerability_finder.rb +1 -1
  21. data/lib/package/audit/services/command_parser.rb +103 -0
  22. data/lib/package/audit/services/package_filter.rb +39 -0
  23. data/lib/package/audit/services/package_finder.rb +58 -0
  24. data/lib/package/audit/{printer.rb → services/package_printer.rb} +12 -11
  25. data/lib/package/audit/{risk_calculator.rb → services/risk_calculator.rb} +8 -4
  26. data/lib/package/audit/technology/detector.rb +40 -0
  27. data/lib/package/audit/technology/validator.rb +56 -0
  28. data/lib/package/audit/util/summary_printer.rb +20 -9
  29. data/lib/package/audit/version.rb +1 -1
  30. data/sig/package/audit/cli.rbs +2 -0
  31. data/sig/package/audit/const/fields.rbs +2 -1
  32. data/sig/package/audit/const/file.rbs +1 -0
  33. data/sig/package/audit/const/yaml.rbs +13 -0
  34. data/sig/package/audit/enum/option.rbs +13 -0
  35. data/sig/package/audit/enum/report.rbs +12 -0
  36. data/sig/package/audit/enum/technology.rbs +12 -0
  37. data/sig/package/audit/{package.rbs → models/package.rbs} +3 -1
  38. data/sig/package/audit/{risk.rbs → models/risk.rbs} +1 -1
  39. data/sig/package/audit/npm/node_collection.rbs +4 -5
  40. data/sig/package/audit/npm/vulnerability_finder.rbs +1 -1
  41. data/sig/package/audit/npm/yarn_lock_parser.rbs +2 -0
  42. data/sig/package/audit/ruby/gem_collection.rbs +4 -1
  43. data/sig/package/audit/services/command_parser.rbs +31 -0
  44. data/sig/package/audit/services/package_filter.rbs +19 -0
  45. data/sig/package/audit/services/package_finder.rbs +23 -0
  46. data/sig/package/audit/{printer.rbs → services/package_printer.rbs} +3 -3
  47. data/sig/package/audit/technology/detector.rbs +19 -0
  48. data/sig/package/audit/technology/validator.rbs +19 -0
  49. data/sig/package/audit/util/summary_printer.rbs +5 -5
  50. metadata +31 -15
  51. data/lib/package/audit/command_service.rb +0 -187
  52. data/sig/package/audit/command_service.rbs +0 -29
  53. /data/lib/package/audit/{risk.rb → models/risk.rb} +0 -0
  54. /data/lib/package/audit/{duplicate_package_merger.rb → services/duplicate_package_merger.rb} +0 -0
  55. /data/sig/package/audit/{duplicate_package_merger.rbs → services/duplicate_package_merger.rbs} +0 -0
  56. /data/sig/package/audit/{risk_calculator.rbs → services/risk_calculator.rbs} +0 -0
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b01c3c377fec4245b836119f812bbacc6ceac724ed3c64da348f0b97481fd2a1
4
- data.tar.gz: ceec2dc5e451fbe9ece1b915e35f790ed2ef60efb18fb3130ccc876e1eb7237c
3
+ metadata.gz: 637173ee31a102187a23134942d6df2245d6b7785708747c85029d5fdf2045c0
4
+ data.tar.gz: d30d4aad7dd1ff9a39348db8125fe991e9877a537eec6962883885f788e79438
5
5
  SHA512:
6
- metadata.gz: '0438ecb657dcaf116dc0048b89a9de474785d88e868f68f306521e917045651b525f49c8a6b802fda8401e8ff795f02b79c0be627fd337dc5eff4dcd48eaf128'
7
- data.tar.gz: 38f68c6e7251196f2a526d4affcbb1487760615dd6947a3bd11b20f6ed0f791bac762a1327741b7c31e72ffa6a6b44471fdcfa98c0e0135d5d77b1751729ce80
6
+ metadata.gz: 187e2559f65548b13eded61cbcc0679e4ddbf99e46484f7ce3a8ae1ddf71d8c095c224dab43aaf3366cc918105a8df4310a2991b69b1a64a23735bd20308f727
7
+ data.tar.gz: 1b452b5db99d6434d475b04adfa91ac4c49dd934211d160a4a9a4d49dafade42e59b7ac2a2162ba344169bc70f74632ffcd466dcfdef5dc99a7379dace932940
@@ -1,11 +1,8 @@
1
+ require_relative 'const/file'
1
2
  require_relative 'const/time'
3
+ require_relative 'enum/option'
4
+ require_relative 'services/command_parser'
2
5
  require_relative 'version'
3
- require_relative 'util/summary_printer'
4
- require_relative 'ruby/bundler_specs'
5
- require_relative 'printer'
6
- require_relative 'ruby/gem_collection'
7
- require_relative 'npm/node_collection'
8
- require_relative 'command_service'
9
6
 
10
7
  require 'json'
11
8
  require 'thor'
@@ -15,57 +12,52 @@ module Package
15
12
  class CLI < Thor
16
13
  default_task :report
17
14
 
15
+ class_option Enum::Option::CONFIG,
16
+ aliases: '-c', banner: 'FILE',
17
+ desc: "Path to a custom configuration file, default: #{Const::File::CONFIG})"
18
+ class_option Enum::Option::TECHNOLOGY,
19
+ aliases: '-t', repeatable: true,
20
+ desc: 'Technology to be audited (repeat this flag for each technology)'
21
+ class_option Enum::Option::INCLUDE_IGNORED,
22
+ type: :boolean, default: false,
23
+ desc: 'Include packages ignored by a configuration file'
24
+ class_option Enum::Option::CSV,
25
+ type: :boolean, default: false,
26
+ desc: 'Output reports using comma separated values (CSV)'
27
+ class_option Enum::Option::CSV_EXCLUDE_HEADERS,
28
+ type: :boolean, default: false,
29
+ desc: "Hide headers when using the --#{Enum::Option::CSV} option"
30
+
31
+ map '-v' => :version
18
32
  map '--version' => :version
19
33
 
20
34
  desc 'report [DIR]', 'Show a report of potentially deprecated, outdated or vulnerable packages'
21
- method_option :csv, type: :boolean, default: false, desc: 'Output using comma separated values (CSV)'
22
- method_option :'exclude-headers', type: :boolean, default: false, desc: 'Hide headers if when using CSV'
23
-
24
35
  def report(dir = Dir.pwd)
25
- # within_rescue_block do
26
- exit CommandService.new(dir, options).all
27
- # end
36
+ within_rescue_block { exit CommandParser.new(dir, options, Enum::Report::ALL).run }
28
37
  end
29
38
 
30
39
  desc 'deprecated [DIR]',
31
40
  "Show packages with no updates by author for at least #{Const::Time::YEARS_ELAPSED_TO_BE_OUTDATED} years"
32
- method_option :csv, type: :boolean, default: false, desc: 'Output using comma separated values (CSV)'
33
- method_option :'exclude-headers', type: :boolean, default: false, desc: 'Hide headers if when using CSV'
34
-
35
41
  def deprecated(dir = Dir.pwd)
36
- within_rescue_block do
37
- exit CommandService.new(dir, options).deprecated
38
- end
42
+ within_rescue_block { exit CommandParser.new(dir, options, Enum::Report::DEPRECATED).run }
39
43
  end
40
44
 
41
45
  desc 'outdated [DIR]', 'Show packages that are out of date'
42
- method_option :csv, type: :boolean, default: false, desc: 'Output using comma separated values (CSV)'
43
- method_option :'exclude-headers', type: :boolean, default: false, desc: 'Hide headers if when using CSV'
44
-
45
46
  def outdated(dir = Dir.pwd)
46
- within_rescue_block do
47
- exit CommandService.new(dir, options).outdated
48
- end
47
+ within_rescue_block { exit CommandParser.new(dir, options, Enum::Report::OUTDATED).run }
49
48
  end
50
49
 
51
50
  desc 'vulnerable [DIR]', 'Show packages and their dependencies that have security vulnerabilities'
52
- method_option :csv, type: :boolean, default: false, desc: 'Output using comma separated values (CSV)'
53
- method_option :'exclude-headers', type: :boolean, default: false, desc: 'Hide headers if when using CSV'
54
-
55
51
  def vulnerable(dir = Dir.pwd)
56
- within_rescue_block do
57
- exit CommandService.new(dir, options).vulnerable
58
- end
52
+ within_rescue_block { exit CommandParser.new(dir, options, Enum::Report::VULNERABLE).run }
59
53
  end
60
54
 
61
55
  desc 'risk', 'Print information on how risk is calculated'
62
-
63
56
  def risk
64
57
  Util::SummaryPrinter.risk
65
58
  end
66
59
 
67
60
  desc 'version', 'Print the currently installed version of the package-audit gem'
68
-
69
61
  def version
70
62
  puts "package-audit #{VERSION}"
71
63
  end
@@ -74,6 +66,14 @@ module Package
74
66
  true
75
67
  end
76
68
 
69
+ def method_missing(command, *args)
70
+ invoke :report, [command], args
71
+ end
72
+
73
+ def respond_to_missing?
74
+ true
75
+ end
76
+
77
77
  private
78
78
 
79
79
  def within_rescue_block
@@ -2,7 +2,7 @@ module Package
2
2
  module Audit
3
3
  module Const
4
4
  module Fields
5
- ALL = %i[
5
+ AVAILABLE = %i[
6
6
  name
7
7
  version
8
8
  version_date
@@ -14,15 +14,15 @@ module Package
14
14
  risk_explanation
15
15
  ]
16
16
 
17
- REPORT = %i[name version latest_version latest_version_date groups vulnerabilities risk_type risk_explanation]
18
- VULNERABLE = %i[name version latest_version groups vulnerabilities]
17
+ ALL = %i[name version latest_version latest_version_date groups vulnerabilities risk_type risk_explanation]
18
+ DEPRECATED = %i[name version latest_version latest_version_date groups]
19
19
  OUTDATED = %i[name version latest_version latest_version_date groups]
20
+ VULNERABLE = %i[name version latest_version groups vulnerabilities]
20
21
 
21
22
  # the names of these fields must match the instance variables in the Dependency class
22
23
  HEADERS = {
23
24
  name: 'Package',
24
25
  version: 'Version',
25
- version_date: 'Date',
26
26
  latest_version: 'Latest',
27
27
  latest_version_date: 'Latest Date',
28
28
  groups: 'Groups',
@@ -2,6 +2,7 @@ module Package
2
2
  module Audit
3
3
  module Const
4
4
  module File
5
+ CONFIG = '.package-audit.yml'
5
6
  GEMFILE = 'Gemfile'
6
7
  GEMFILE_LOCK = 'Gemfile.lock'
7
8
  PACKAGE_JSON = 'package.json'
@@ -0,0 +1,13 @@
1
+ module Package
2
+ module Audit
3
+ module Const
4
+ module YAML
5
+ DEPRECATED = 'deprecated'
6
+ OUTDATED = 'outdated'
7
+ VULNERABLE = 'vulnerable'
8
+ TECHNOLOGY = 'technology'
9
+ VERSION = 'version'
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,13 @@
1
+ module Package
2
+ module Audit
3
+ module Enum
4
+ module Option
5
+ CONFIG = 'config'
6
+ CSV = 'csv'
7
+ CSV_EXCLUDE_HEADERS = 'exclude-headers'
8
+ INCLUDE_IGNORED = 'include-ignored'
9
+ TECHNOLOGY = 'technology'
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,12 @@
1
+ module Package
2
+ module Audit
3
+ module Enum
4
+ module Report
5
+ ALL = :all
6
+ DEPRECATED = :deprecated
7
+ OUTDATED = :outdated
8
+ VULNERABLE = :vulnerable
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,14 @@
1
+ module Package
2
+ module Audit
3
+ module Enum
4
+ module Technology
5
+ NODE = 'node'
6
+ RUBY = 'ruby'
7
+
8
+ def self.all
9
+ constants.map { |key| Enum::Technology.const_get(key) }.sort
10
+ end
11
+ end
12
+ end
13
+ end
14
+ end
@@ -1,5 +1,5 @@
1
- require_relative 'base'
2
1
  require_relative '../util/bash_color'
2
+ require_relative 'base'
3
3
 
4
4
  module Package
5
5
  module Audit
@@ -1,5 +1,5 @@
1
- require_relative 'base'
2
1
  require_relative '../util/bash_color'
2
+ require_relative 'base'
3
3
 
4
4
  module Package
5
5
  module Audit
@@ -1,6 +1,6 @@
1
- require_relative 'base'
2
1
  require_relative '../const/time'
3
2
  require_relative '../util/bash_color'
3
+ require_relative 'base'
4
4
 
5
5
  require 'time'
6
6
 
@@ -1,6 +1,6 @@
1
- require_relative 'base'
2
1
  require_relative '../enum/vulnerability_type'
3
2
  require_relative '../util/bash_color'
3
+ require_relative 'base'
4
4
 
5
5
  module Package
6
6
  module Audit
@@ -1,18 +1,19 @@
1
+ require_relative '../enum/environment'
2
+ require_relative '../enum/risk_explanation'
3
+ require_relative '../enum/risk_type'
4
+ require_relative '../services/risk_calculator'
1
5
  require_relative 'risk'
2
- require_relative 'risk_calculator'
3
- require_relative 'enum/environment'
4
- require_relative 'enum/risk_type'
5
- require_relative 'enum/risk_explanation'
6
6
 
7
7
  module Package
8
8
  module Audit
9
9
  class Package
10
- attr_reader :name, :version
10
+ attr_reader :name, :version, :technology
11
11
  attr_accessor :groups, :version_date, :latest_version, :latest_version_date, :vulnerabilities
12
12
 
13
- def initialize(name, version, **attr)
13
+ def initialize(name, version, technology, **attr)
14
14
  @name = name.to_s
15
15
  @version = version.to_s
16
+ @technology = technology.to_s
16
17
  @groups = []
17
18
  @vulnerabilities = []
18
19
  @risks = []
@@ -1,18 +1,29 @@
1
- require_relative 'yarn_lock_parser'
1
+ require_relative '../const/file'
2
+ require_relative '../services/duplicate_package_merger'
2
3
  require_relative 'npm_meta_data'
3
4
  require_relative 'vulnerability_finder'
4
- require_relative '../duplicate_package_merger'
5
+ require_relative 'yarn_lock_parser'
5
6
 
6
7
  module Package
7
8
  module Audit
8
9
  module Npm
9
10
  class NodeCollection
10
- PACKAGE_JSON = 'package.json'
11
- PACKAGE_LOCK = 'package-lock.json'
12
- YARN_LOCK = 'yarn.lock'
13
-
14
- def initialize(dir)
11
+ def initialize(dir, report)
15
12
  @dir = dir
13
+ @report = report
14
+ end
15
+
16
+ def fetch
17
+ case @report
18
+ when Enum::Report::DEPRECATED
19
+ deprecated
20
+ when Enum::Report::OUTDATED
21
+ outdated
22
+ when Enum::Report::VULNERABLE
23
+ vulnerable
24
+ else
25
+ all
26
+ end
16
27
  end
17
28
 
18
29
  def all
@@ -44,7 +55,7 @@ module Package
44
55
  private
45
56
 
46
57
  def fetch_from_package_json
47
- package_json = JSON.parse(File.read("#{@dir}/#{PACKAGE_JSON}"), symbolize_names: true)
58
+ package_json = JSON.parse(File.read("#{@dir}/#{Const::File::PACKAGE_JSON}"), symbolize_names: true)
48
59
  default_deps = package_json[:dependencies] || {}
49
60
  dev_deps = package_json[:devDependencies] || {}
50
61
  [default_deps, dev_deps]
@@ -52,8 +63,8 @@ module Package
52
63
 
53
64
  def fetch_from_lock_file
54
65
  default_deps, dev_deps = fetch_from_package_json
55
- if File.exist?("#{@dir}/#{YARN_LOCK}")
56
- YarnLockParser.new("#{@dir}/#{YARN_LOCK}").fetch(default_deps || {}, dev_deps || {})
66
+ if File.exist?("#{@dir}/#{Const::File::YARN_LOCK}")
67
+ YarnLockParser.new("#{@dir}/#{Const::File::YARN_LOCK}").fetch(default_deps || {}, dev_deps || {})
57
68
  else
58
69
  []
59
70
  end
@@ -34,7 +34,7 @@ module Package
34
34
  full_name = "#{name}@#{version}"
35
35
  vulnerability = advisory[:severity] || Enum::VulnerabilityType::UNKNOWN
36
36
 
37
- @vuln_hash[full_name] = Package.new(name, version) unless @vuln_hash.key? full_name
37
+ @vuln_hash[full_name] = Package.new(name, version, 'node') unless @vuln_hash.key? full_name
38
38
  @vuln_hash[full_name].update vulnerabilities: @vuln_hash[full_name].vulnerabilities + [vulnerability]
39
39
  @vuln_hash[full_name].update groups: @pkg_hash[parent_name].groups
40
40
  end
@@ -12,7 +12,7 @@ module Package
12
12
  default_deps.merge(dev_deps).each do |dep_name, expected_version|
13
13
  pkg_block = fetch_package_block(dep_name, expected_version)
14
14
  version = fetch_package_version(dep_name, pkg_block)
15
- pks = Package.new(dep_name.to_s, version)
15
+ pks = Package.new(dep_name.to_s, version, 'node')
16
16
  pks.update groups: dev_deps.key?(dep_name) ? %i[development] : %i[default development]
17
17
  pkgs << pks
18
18
  end
@@ -22,7 +22,7 @@ module Package
22
22
  private
23
23
 
24
24
  def fetch_package_block(dep_name, expected_version)
25
- regex = /#{Regexp.escape(dep_name)}@#{Regexp.escape(expected_version)}.*?:.*?(\n\n|\z)/m
25
+ regex = regex_pattern_for_package(dep_name, expected_version)
26
26
  blocks = @yarn_lock_file.match(regex)
27
27
  if blocks.nil? || blocks[0].nil?
28
28
  raise NoMatchingPatternError, "Unable to find \"#{dep_name}\" in #{@yarn_lock_path}"
@@ -40,6 +40,17 @@ module Package
40
40
 
41
41
  version || '0.0.0.0'
42
42
  end
43
+
44
+ def regex_pattern_for_package(dep_name, version)
45
+ # assume the package name is prefixed by a space, a quote or be the first thing on the line
46
+ # there can be multiple comma-separated versions on the same line with or without quotes
47
+ # Here are some examples of strings that would be matched:
48
+ # - aria-query@^5.0.0:
49
+ # - lodash@^4.17.15, lodash@^4.17.20:
50
+ # - "@adobe/css-tools@^4.0.1":
51
+ # - "@babel/runtime@^7.23.1", "@babel/runtime@^7.9.2":
52
+ /(?:^|[ "])#{Regexp.escape(dep_name)}@#{Regexp.escape(version)}.*?:.*?(\n\n|\z)/m
53
+ end
43
54
  end
44
55
  end
45
56
  end
@@ -1,4 +1,4 @@
1
- require_relative '../package'
1
+ require_relative '../models/package'
2
2
  require_relative 'gem_meta_data'
3
3
  require_relative 'vulnerability_finder'
4
4
 
@@ -1,18 +1,35 @@
1
- require_relative 'bundler_specs'
1
+ require_relative '../enum/report'
2
2
  require_relative '../enum/risk_type'
3
- require_relative '../duplicate_package_merger'
3
+ require_relative '../services/duplicate_package_merger'
4
+ require_relative 'bundler_specs'
4
5
 
5
6
  module Package
6
7
  module Audit
7
8
  module Ruby
8
9
  class GemCollection
9
- def initialize(dir)
10
+ def initialize(dir, report)
10
11
  @dir = dir
12
+ @report = report
11
13
  end
12
14
 
15
+ def fetch
16
+ case @report
17
+ when Enum::Report::DEPRECATED
18
+ deprecated
19
+ when Enum::Report::OUTDATED
20
+ outdated
21
+ when Enum::Report::VULNERABLE
22
+ vulnerable
23
+ else
24
+ all
25
+ end
26
+ end
27
+
28
+ private
29
+
13
30
  def all
14
31
  specs = BundlerSpecs.gemfile(@dir)
15
- pkgs = specs.map { |spec| Package.new(spec.name, spec.version) }
32
+ pkgs = specs.map { |spec| Package.new(spec.name, spec.version, Enum::Technology::RUBY) }
16
33
  vulnerable_pkgs = VulnerabilityFinder.new(@dir).run
17
34
  pkgs = GemMetaData.new(pkgs + vulnerable_pkgs).fetch.filter(&:risk?)
18
35
  DuplicatePackageMerger.new(pkgs).run
@@ -20,14 +37,14 @@ module Package
20
37
 
21
38
  def deprecated
22
39
  specs = BundlerSpecs.gemfile(@dir)
23
- pkgs = specs.map { |spec| Package.new(spec.name, spec.version) }
40
+ pkgs = specs.map { |spec| Package.new(spec.name, spec.version, Enum::Technology::RUBY) }
24
41
  pkgs = GemMetaData.new(pkgs).fetch.filter(&:deprecated?)
25
42
  DuplicatePackageMerger.new(pkgs).run
26
43
  end
27
44
 
28
45
  def outdated(include_implicit: false)
29
46
  specs = include_implicit ? BundlerSpecs.all(@dir) : BundlerSpecs.gemfile(@dir)
30
- pkgs = specs.map { |spec| Package.new(spec.name, spec.version) }
47
+ pkgs = specs.map { |spec| Package.new(spec.name, spec.version, Enum::Technology::RUBY) }
31
48
  pkgs = GemMetaData.new(pkgs).fetch.filter(&:outdated?)
32
49
  DuplicatePackageMerger.new(pkgs).run
33
50
  end
@@ -1,4 +1,4 @@
1
- require_relative '../package'
1
+ require_relative '../models/package'
2
2
 
3
3
  module Package
4
4
  module Audit
@@ -26,7 +26,7 @@ module Package
26
26
  version = json[:gem][:version]
27
27
  full_name = "#{name}@#{version}"
28
28
  vulnerability = json[:advisory][:criticality] || Enum::VulnerabilityType::UNKNOWN
29
- @vuln_hash[full_name] = Package.new(name, version) unless @vuln_hash.key? full_name
29
+ @vuln_hash[full_name] = Package.new(name, version, 'ruby') unless @vuln_hash.key? full_name
30
30
  @vuln_hash[full_name].update vulnerabilities: @vuln_hash[full_name].vulnerabilities + [vulnerability]
31
31
  end
32
32
  end
@@ -0,0 +1,103 @@
1
+ require_relative '../const/cmd'
2
+ require_relative '../const/file'
3
+ require_relative '../enum/option'
4
+ require_relative '../enum/report'
5
+ require_relative '../technology/detector'
6
+ require_relative '../technology/validator'
7
+ require_relative '../util/summary_printer'
8
+ require_relative 'package_finder'
9
+ require_relative 'package_printer'
10
+
11
+ require 'yaml'
12
+
13
+ module Package
14
+ module Audit
15
+ class CommandParser
16
+ def initialize(dir, options, report)
17
+ @dir = dir
18
+ @options = options
19
+ @report = report
20
+ @config = parse_config_file
21
+ @technologies = parse_technologies
22
+ end
23
+
24
+ def run
25
+ cumulative_pkgs = []
26
+
27
+ @technologies.each do |technology|
28
+ all_pkgs, ignored_pkgs = PackageFinder.new(@config, @dir, @report).run(technology)
29
+ ignored_pkgs = [] if @options[Enum::Option::INCLUDE_IGNORED]
30
+ cumulative_pkgs << all_pkgs
31
+ print_results(technology, (all_pkgs || []) - (ignored_pkgs || []), ignored_pkgs || [])
32
+ end
33
+
34
+ cumulative_pkgs.any?
35
+ end
36
+
37
+ private
38
+
39
+ def print_results(technology, pkgs, ignored_pkgs)
40
+ PackagePrinter.new(@options, pkgs).print(report_fields)
41
+ print_summary(technology, pkgs, ignored_pkgs) unless @options[Enum::Option::CSV]
42
+ print_disclaimer(technology) unless @options[Enum::Option::CSV] || pkgs.empty?
43
+ end
44
+
45
+ def print_summary(technology, pkgs, ignored_pkgs)
46
+ if @report == Enum::Report::ALL
47
+ Util::SummaryPrinter.statistics(technology, @report, pkgs, ignored_pkgs)
48
+ else
49
+ Util::SummaryPrinter.total(technology, @report, pkgs, ignored_pkgs)
50
+ end
51
+ end
52
+
53
+ def print_disclaimer(technology)
54
+ case @report
55
+ when Enum::Report::DEPRECATED
56
+ Util::SummaryPrinter.deprecated
57
+ when Enum::Report::ALL, Enum::Report::VULNERABLE
58
+ Util::SummaryPrinter.vulnerable(technology, learn_more_command(technology))
59
+ end
60
+ end
61
+
62
+ def learn_more_command(technology)
63
+ case technology
64
+ when Enum::Technology::RUBY
65
+ Const::Cmd::BUNDLE_AUDIT
66
+ when Enum::Technology::NODE
67
+ Const::Cmd::YARN_AUDIT
68
+ else
69
+ raise ArgumentError, "Unexpected technology \"#{technology}\" found in #{__method__}"
70
+ end
71
+ end
72
+
73
+ def report_fields
74
+ case @report
75
+ when Enum::Report::DEPRECATED
76
+ Const::Fields::DEPRECATED
77
+ when Enum::Report::OUTDATED
78
+ Const::Fields::OUTDATED
79
+ when Enum::Report::VULNERABLE
80
+ Const::Fields::VULNERABLE
81
+ else
82
+ Const::Fields::ALL
83
+ end
84
+ end
85
+
86
+ def parse_config_file
87
+ if @options[Enum::Option::CONFIG].nil?
88
+ YAML.load_file("#{@dir}/#{Const::File::CONFIG}") if File.exist? "#{@dir}/#{Const::File::CONFIG}"
89
+ elsif File.exist? @options[Enum::Option::CONFIG]
90
+ YAML.load_file(@options[Enum::Option::CONFIG])
91
+ else
92
+ raise ArgumentError, "Configuration file not found: #{@options[Enum::Option::CONFIG]}"
93
+ end
94
+ end
95
+
96
+ def parse_technologies
97
+ technology_validator = Technology::Validator.new(@dir)
98
+ @options[Enum::Option::TECHNOLOGY]&.each { |technology| technology_validator.validate! technology }
99
+ @options[Enum::Option::TECHNOLOGY] || Technology::Detector.new(@dir).detect
100
+ end
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,39 @@
1
+ require_relative '../const/cmd'
2
+ require_relative '../const/file'
3
+ require_relative '../const/yaml'
4
+ require_relative '../enum/technology'
5
+ require_relative '../ruby/gem_collection'
6
+
7
+ require 'yaml'
8
+
9
+ module Package
10
+ module Audit
11
+ class PackageFilter
12
+ def initialize(config)
13
+ @config = config
14
+ end
15
+
16
+ def ignored?(pkg)
17
+ pkg_yaml = pkg_yaml_from_config(pkg)
18
+ pkg_version_in_config?(pkg, pkg_yaml) && ignore_package?(pkg, pkg_yaml)
19
+ end
20
+
21
+ private
22
+
23
+ def pkg_yaml_from_config(pkg)
24
+ yaml_fragment = @config&.dig(Const::YAML::TECHNOLOGY, pkg.technology, pkg.name)&.to_yaml
25
+ yaml_fragment.nil? ? nil : YAML.safe_load(yaml_fragment)
26
+ end
27
+
28
+ def pkg_version_in_config?(pkg, yaml)
29
+ yaml&.dig(Const::YAML::VERSION) == pkg.version
30
+ end
31
+
32
+ def ignore_package?(pkg, yaml)
33
+ (!pkg.deprecated? || yaml&.dig(Const::YAML::DEPRECATED) == false) &&
34
+ (!pkg.outdated? || yaml&.dig(Const::YAML::OUTDATED) == false) &&
35
+ (!pkg.vulnerable? || yaml&.dig(Const::YAML::VULNERABLE) == false)
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,58 @@
1
+ require_relative '../const/cmd'
2
+ require_relative '../const/file'
3
+ require_relative '../const/yaml'
4
+ require_relative '../enum/technology'
5
+ require_relative '../npm/node_collection'
6
+ require_relative '../ruby/gem_collection'
7
+ require_relative 'package_filter'
8
+
9
+ require 'yaml'
10
+
11
+ module Package
12
+ module Audit
13
+ class PackageFinder
14
+ def initialize(config, dir, report)
15
+ @config = config
16
+ @dir = dir
17
+ @report = report
18
+ end
19
+
20
+ def run(technology)
21
+ all_pkgs = find_by_technology(technology)
22
+ ignored_pkgs = filter_pkgs_based_on_config(all_pkgs)
23
+ [all_pkgs, ignored_pkgs]
24
+ end
25
+
26
+ private
27
+
28
+ def find_by_technology(technology)
29
+ case technology
30
+ when Enum::Technology::RUBY
31
+ find_ruby
32
+ when Enum::Technology::NODE
33
+ find_node
34
+ else
35
+ []
36
+ end
37
+ end
38
+
39
+ def find_node
40
+ Npm::NodeCollection.new(@dir, @report).fetch
41
+ end
42
+
43
+ def find_ruby
44
+ Ruby::GemCollection.new(@dir, @report).fetch
45
+ end
46
+
47
+ def filter_pkgs_based_on_config(pkgs)
48
+ package_filter = PackageFilter.new(@config)
49
+ ignored_pkgs = []
50
+
51
+ pkgs.each do |pkg|
52
+ ignored_pkgs << pkg if package_filter.ignored?(pkg)
53
+ end
54
+ ignored_pkgs
55
+ end
56
+ end
57
+ end
58
+ end