gem_checks 0.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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 63d6aced5b4e03fb47dee58279f283b68dc66b6e
4
+ data.tar.gz: 8576824fd844c3f74dced5c3b4e0d0790278eda7
5
+ SHA512:
6
+ metadata.gz: 6161980f198a53d83f19e959559e928bab9113f26414102203b629693e12a73d66312314682da45fc467b05494bd1ed705431e71e3f4d3e4f57c0a34e894ff92
7
+ data.tar.gz: 62683b7e6aeb99cc777ab0c2eec73494b20ccbbfe722ab491288a7d2733a757aea6ddd702092c5c3f651bf0591163680a16ce20c5b37ac54dde6200ccaa9a7b9
data/bin/gem_checks ADDED
@@ -0,0 +1,37 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $:.unshift File.expand_path('../../lib', __FILE__)
4
+ require 'gem_checks'
5
+ require 'optparse'
6
+
7
+ ARGV.push('-h') if ARGV.empty?
8
+
9
+ options = {}
10
+ OptionParser.new do |opts|
11
+ opts.banner = 'Usage: gem_checks /path/to/Gemfile.lock [options]'
12
+
13
+ opts.on('-h', '--help', 'Prints this help') do
14
+ puts opts
15
+ exit
16
+ end
17
+ opts.on('-v', '--verbose', 'Prints verbose logging') do |v|
18
+ options[:verbose] = v
19
+ end
20
+ end.parse!
21
+
22
+ # Get file path from the ARGs list
23
+ file_path = ARGV.first
24
+ lockfile = File.new(file_path)
25
+
26
+ # Setup the client logger
27
+ client = GemnasiumClient.new
28
+ if options[:verbose]
29
+ logger = Logger.new(STDOUT)
30
+ client.set_logger(logger)
31
+ end
32
+ vulnerable_version_check = VulnerableVersionCheck.new(gemnasium_client: client)
33
+
34
+ # Evaluate the lockfile
35
+ GemChecks.new(lockfile: lockfile,
36
+ vulnerable_version_check: vulnerable_version_check)
37
+ .display_vulnerable_gems
@@ -0,0 +1,17 @@
1
+ class DependencyParser
2
+ def parse(lockfile)
3
+ lockfile.map { |line| parse_line(line) }.compact
4
+ end
5
+
6
+ private
7
+
8
+ def parse_line(line)
9
+ (match = line.match(GEM_PARSER)) ? parse_gem(match) : nil
10
+ end
11
+
12
+ def parse_gem(gem_str)
13
+ { gem_name: gem_str[:gem_name], version: gem_str[:version] }
14
+ end
15
+
16
+ GEM_PARSER = /(?<gem_name>([a-zA-Z\-_0-9.])+)\s?\((?<version>(\d+\.?)+)\)/
17
+ end
@@ -0,0 +1,37 @@
1
+ require 'delegate'
2
+ class GemCollection < SimpleDelegator
3
+ def initialize(collection)
4
+ @collection = collection
5
+ super(collection)
6
+ end
7
+
8
+ def display_vulnerable
9
+ collection.each do |gem|
10
+ puts format_gem_message(gem)
11
+ end
12
+ end
13
+
14
+ def self.wrap(collection)
15
+ collection.empty? ? EmptyGemCollection.new : new(collection)
16
+ end
17
+
18
+ private
19
+
20
+ attr_reader :collection
21
+
22
+ def format_gem_message(gem)
23
+ "#{gem[:gem_name]}, version: #{gem[:version]}"
24
+ end
25
+
26
+ class EmptyGemCollection
27
+ MESSAGE = "\nYou have no vulnerable gems in your project"
28
+
29
+ def empty?
30
+ true
31
+ end
32
+
33
+ def display_vulnerable
34
+ puts MESSAGE
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,52 @@
1
+ require 'gem_checks/simple_logger'
2
+ require 'logger'
3
+ require 'nokogiri'
4
+ require 'open-uri'
5
+
6
+ class GemnasiumClient
7
+ def initialize(logger: SimpleLogger.new)
8
+ @logger = set_log_level(logger)
9
+ end
10
+
11
+ def vulnerable?(gem_name:, version:)
12
+ uri = client_url(gem_name, version)
13
+ log_analysis(gem_name, version)
14
+ query_gemnasium(uri)
15
+ end
16
+
17
+ def set_logger(logger)
18
+ @logger = set_log_level(logger)
19
+ end
20
+
21
+ private
22
+
23
+ def log_analysis(gem_name, version)
24
+ @logger.info("Analysing: #{gem_name}, version: #{version}")
25
+ end
26
+
27
+ def set_log_level(logger)
28
+ level = ENV.fetch('LOG_LEVEL') { Logger::INFO }.to_i
29
+ logger.tap do |logger|
30
+ logger.level = level
31
+ end
32
+ end
33
+
34
+ def gem_vulnerable?(doc)
35
+ doc.css('div.accordion.advisory.affected').count >= 1
36
+ end
37
+
38
+ def query_gemnasium(uri)
39
+ begin
40
+ open(uri) do |gemnasium_raw|
41
+ doc = Nokogiri::HTML(gemnasium_raw)
42
+ gem_vulnerable?(doc)
43
+ end
44
+ rescue OpenURI::HTTPError
45
+ false
46
+ end
47
+ end
48
+
49
+ def client_url(gem_name, version)
50
+ "https://gemnasium.com/gems/#{gem_name}/versions/#{version}"
51
+ end
52
+ end
@@ -0,0 +1,47 @@
1
+ class SimpleLogger
2
+ LOG_LEVELS = [:debug, :info, :warn, :error, :fatal, :unknown]
3
+
4
+ def initialize(default_level: :debug)
5
+ @level = LOG_LEVELS.index(default_level)
6
+ end
7
+
8
+ def debug(*)
9
+ log_if_level_valid(:debug)
10
+ end
11
+
12
+ def info(*)
13
+ log_if_level_valid(:info)
14
+ end
15
+
16
+ def warn(*)
17
+ log_if_level_valid(:warn)
18
+ end
19
+
20
+ def error(*)
21
+ log_level_if_valid(:error)
22
+ end
23
+
24
+ def fatal(*)
25
+ log_level_if_valid(:fatal)
26
+ end
27
+
28
+ def unknown(*)
29
+ log_level_if_valid(:unknown)
30
+ end
31
+
32
+ def level=(level)
33
+ @level = level
34
+ end
35
+
36
+ private
37
+
38
+ def log_if_level_valid(level_sym)
39
+ if @level <= LOG_LEVELS.index(level_sym)
40
+ log
41
+ end
42
+ end
43
+
44
+ def log(*)
45
+ print '.'
46
+ end
47
+ end
@@ -0,0 +1,3 @@
1
+ class Version
2
+ VERSION = '0.0.1'
3
+ end
@@ -0,0 +1,17 @@
1
+ require 'gem_checks/gemnasium_client'
2
+
3
+ class VulnerableVersionCheck
4
+ def initialize(gemnasium_client: GemnasiumClient.new)
5
+ @gemnasium_client = gemnasium_client
6
+ end
7
+
8
+ def call(deps)
9
+ deps.select { |gem| vulnerable?(gem) }
10
+ end
11
+
12
+ private
13
+
14
+ def vulnerable?(gem)
15
+ @gemnasium_client.vulnerable?(gem)
16
+ end
17
+ end
data/lib/gem_checks.rb ADDED
@@ -0,0 +1,33 @@
1
+ require 'gem_checks/dependency_parser'
2
+ require 'gem_checks/vulnerable_version_check'
3
+ require 'gem_checks/gem_collection'
4
+
5
+ class GemChecks
6
+ def initialize(dependency_parser: DependencyParser.new,
7
+ vulnerable_version_check: VulnerableVersionCheck.new,
8
+ lockfile:)
9
+ @dependency_parser = dependency_parser
10
+ @vulnerable_version_check = vulnerable_version_check
11
+ @lockfile = lockfile
12
+ end
13
+
14
+ def display_vulnerable_gems
15
+ GemCollection.wrap(evaluate).display_vulnerable
16
+ end
17
+
18
+ private
19
+
20
+ def evaluate
21
+ dependencies = parse_dependencies
22
+ list_vulnerable(dependencies)
23
+ end
24
+
25
+ def parse_dependencies
26
+ @dependency_parser.parse(@lockfile)
27
+ end
28
+
29
+ def list_vulnerable(deps)
30
+ @vulnerable_version_check.call(deps)
31
+ end
32
+
33
+ end
@@ -0,0 +1,11 @@
1
+ require 'spec_helper'
2
+ require 'gem_checks/dependency_parser'
3
+
4
+ RSpec.describe DependencyParser do
5
+ describe '#parse' do
6
+ it 'parses the file into a list' do
7
+ lockfile = open_safe_file
8
+ expect(subject.parse(lockfile)).to eq(safe_parsed_results)
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,33 @@
1
+ require 'spec_helper'
2
+ require 'gem_checks'
3
+
4
+ RSpec.describe GemChecks do
5
+ class MockVulnerableVersionCheck
6
+ def call(deps)
7
+ return [] unless deps.include?(vulnerable_gem)
8
+ [vulnerable_gem]
9
+ end
10
+ end
11
+
12
+ describe '#display_vulnerable_gems' do
13
+ context 'with no vulnerabilities in the lockfile' do
14
+
15
+ it 'displays the empty collection message' do
16
+ lockfile = open_safe_file
17
+ subject = GemChecks.new(vulnerable_version_check: MockVulnerableVersionCheck.new,
18
+ lockfile: lockfile)
19
+ expect{ subject.display_vulnerable_gems }.to output(empty_collection_message).to_stdout
20
+ end
21
+ end
22
+
23
+ context 'with one vulnerability in the lockfile' do
24
+
25
+ it 'displays the vulnerable gem' do
26
+ lockfile = open_unsafe_one_vuln_file
27
+ subject = GemChecks.new(vulnerable_version_check: MockVulnerableVersionCheck.new,
28
+ lockfile: lockfile)
29
+ expect{ subject.display_vulnerable_gems }.to output(format_gem_message(vulnerable_gem)).to_stdout
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,33 @@
1
+ require 'spec_helper'
2
+ require 'gem_checks/gem_collection'
3
+
4
+ RSpec.describe GemCollection do
5
+ describe '.wrap' do
6
+ it 'delegates to the underlying collection' do
7
+ expect(GemCollection.wrap([])).to be_empty
8
+ end
9
+ end
10
+
11
+ describe '#display_vulnerable' do
12
+ context 'when the collection is empty' do
13
+ it 'displays a message that no gems are vulnerable' do
14
+ assert_wrapped_collection_message(collection: [],
15
+ message: empty_collection_message)
16
+ end
17
+ end
18
+
19
+ context 'when the collection is not empty' do
20
+ it 'displays the vulnerable gems' do
21
+ assert_wrapped_collection_message(collection: [vulnerable_gem],
22
+ message: format_gem_message(vulnerable_gem))
23
+ end
24
+ end
25
+ end
26
+
27
+ private
28
+
29
+ def assert_wrapped_collection_message(collection:, message:)
30
+ collection = GemCollection.wrap(collection)
31
+ expect{ collection.display_vulnerable }.to output(message).to_stdout
32
+ end
33
+ end
@@ -0,0 +1,20 @@
1
+ require 'spec_helper'
2
+ require 'gem_checks/gemnasium_client'
3
+
4
+ RSpec.describe GemnasiumClient do
5
+ describe '#vulnerable?', speed: 'slow' do
6
+ context 'when the gem is not vulnerable' do
7
+ it 'returns false' do
8
+ gem = safe_parsed_results.last
9
+ expect(subject.vulnerable?(gem)).to be_falsey
10
+ end
11
+ end
12
+
13
+ context 'when the gem is vulnerable' do
14
+ it 'returns true' do
15
+ gem = vulnerable_parsed_results.last
16
+ expect(subject.vulnerable?(gem)).to be_truthy
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,20 @@
1
+ require 'spec_helper'
2
+ require 'gem_checks/simple_logger'
3
+
4
+ RSpec.describe SimpleLogger do
5
+ describe '#info' do
6
+ context 'when the log level is set to info' do
7
+ it 'prints a "."' do
8
+ logger = SimpleLogger.new
9
+ expect { logger.info }.to output('.').to_stdout
10
+ end
11
+ end
12
+
13
+ context 'when the log level is set to warn' do
14
+ it 'does not output anything' do
15
+ logger = SimpleLogger.new(default_level: :warn)
16
+ expect { logger.info }.not_to output('.').to_stdout
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,26 @@
1
+ require 'spec_helper'
2
+ require 'gem_checks/vulnerable_version_check'
3
+
4
+ RSpec.describe VulnerableVersionCheck do
5
+ class MockGemnasiumClient
6
+ def vulnerable?(gem)
7
+ gem == vulnerable_gem
8
+ end
9
+ end
10
+
11
+ describe '#call' do
12
+ context 'when there are no vulnerable gems' do
13
+ it 'returns an empty collection' do
14
+ subject = VulnerableVersionCheck.new(gemnasium_client: MockGemnasiumClient.new)
15
+ expect(subject.call(safe_parsed_results)).to be_empty
16
+ end
17
+ end
18
+
19
+ context 'when there are vulnerable gems' do
20
+ it 'returns a collection of vulnerable gems' do
21
+ subject = VulnerableVersionCheck.new(gemnasium_client: MockGemnasiumClient.new)
22
+ expect(subject.call(vulnerable_parsed_results)).to include(vulnerable_gem)
23
+ end
24
+ end
25
+ end
26
+ end
metadata ADDED
@@ -0,0 +1,124 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: gem_checks
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Jacob Chae
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2016-04-06 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rspec
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '3.4'
20
+ - - ">="
21
+ - !ruby/object:Gem::Version
22
+ version: 3.4.0
23
+ type: :development
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - "~>"
28
+ - !ruby/object:Gem::Version
29
+ version: '3.4'
30
+ - - ">="
31
+ - !ruby/object:Gem::Version
32
+ version: 3.4.0
33
+ - !ruby/object:Gem::Dependency
34
+ name: rake
35
+ requirement: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: '11.1'
40
+ - - ">="
41
+ - !ruby/object:Gem::Version
42
+ version: 11.1.0
43
+ type: :development
44
+ prerelease: false
45
+ version_requirements: !ruby/object:Gem::Requirement
46
+ requirements:
47
+ - - "~>"
48
+ - !ruby/object:Gem::Version
49
+ version: '11.1'
50
+ - - ">="
51
+ - !ruby/object:Gem::Version
52
+ version: 11.1.0
53
+ - !ruby/object:Gem::Dependency
54
+ name: nokogiri
55
+ requirement: !ruby/object:Gem::Requirement
56
+ requirements:
57
+ - - "~>"
58
+ - !ruby/object:Gem::Version
59
+ version: 1.6.7
60
+ - - ">="
61
+ - !ruby/object:Gem::Version
62
+ version: 1.6.7.2
63
+ type: :runtime
64
+ prerelease: false
65
+ version_requirements: !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - "~>"
68
+ - !ruby/object:Gem::Version
69
+ version: 1.6.7
70
+ - - ">="
71
+ - !ruby/object:Gem::Version
72
+ version: 1.6.7.2
73
+ description: Validate your Gemfile.lock against Gemnasium.
74
+ email: jacob.chae@mobiledefense.com
75
+ executables:
76
+ - gem_checks
77
+ extensions: []
78
+ extra_rdoc_files: []
79
+ files:
80
+ - bin/gem_checks
81
+ - lib/gem_checks.rb
82
+ - lib/gem_checks/dependency_parser.rb
83
+ - lib/gem_checks/gem_collection.rb
84
+ - lib/gem_checks/gemnasium_client.rb
85
+ - lib/gem_checks/simple_logger.rb
86
+ - lib/gem_checks/version.rb
87
+ - lib/gem_checks/vulnerable_version_check.rb
88
+ - spec/dependency_parser_spec.rb
89
+ - spec/gem_checks_spec.rb
90
+ - spec/gem_collection_spec.rb
91
+ - spec/gemnasium_client_spec.rb
92
+ - spec/simple_logger_spec.rb
93
+ - spec/vulnerable_version_check_spec.rb
94
+ homepage: https://github.com/mobiledefense/gem_checks
95
+ licenses:
96
+ - Apache
97
+ metadata: {}
98
+ post_install_message:
99
+ rdoc_options: []
100
+ require_paths:
101
+ - lib
102
+ required_ruby_version: !ruby/object:Gem::Requirement
103
+ requirements:
104
+ - - ">="
105
+ - !ruby/object:Gem::Version
106
+ version: 2.1.0
107
+ required_rubygems_version: !ruby/object:Gem::Requirement
108
+ requirements:
109
+ - - ">="
110
+ - !ruby/object:Gem::Version
111
+ version: '0'
112
+ requirements: []
113
+ rubyforge_project:
114
+ rubygems_version: 2.4.5.1
115
+ signing_key:
116
+ specification_version: 4
117
+ summary: Validate your Gemfile.lock against Gemnasium.
118
+ test_files:
119
+ - spec/dependency_parser_spec.rb
120
+ - spec/gem_checks_spec.rb
121
+ - spec/gem_collection_spec.rb
122
+ - spec/gemnasium_client_spec.rb
123
+ - spec/simple_logger_spec.rb
124
+ - spec/vulnerable_version_check_spec.rb