gem_checks 0.0.1

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