lib/bundler/audit/scanner.rb in bundler-audit-0.7.0.1 vs lib/bundler/audit/scanner.rb in bundler-audit-0.8.0.rc1
- old
+ new
@@ -1,24 +1,40 @@
+#
+# Copyright (c) 2013-2020 Hal Brodigan (postmodern.mod3 at gmail.com)
+#
+# bundler-audit is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# bundler-audit is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with bundler-audit. If not, see <http://www.gnu.org/licenses/>.
+#
+
require 'bundler'
+require 'bundler/audit/configuration'
require 'bundler/audit/database'
+require 'bundler/audit/report'
+require 'bundler/audit/results/insecure_source'
+require 'bundler/audit/results/unpatched_gem'
require 'bundler/lockfile_parser'
require 'ipaddr'
require 'resolv'
require 'set'
require 'uri'
+require 'yaml'
module Bundler
module Audit
class Scanner
- # Represents a plain-text source
- InsecureSource = Struct.new(:source)
-
- # Represents a gem that is covered by an Advisory
- UnpatchedGem = Struct.new(:gem, :advisory)
-
# The advisory database
#
# @return [Database]
attr_reader :database
@@ -28,28 +44,83 @@
# The parsed `Gemfile.lock` from the project
#
# @return [Bundler::LockfileParser]
attr_reader :lockfile
+ # The configuration loaded from the `.bundler-audit.yml` file from the project
#
+ # @return [Hash]
+ attr_reader :config
+
+ #
# Initializes a scanner.
#
# @param [String] root
# The path to the project root.
#
# @param [String] gemfile_lock
# Alternative name for the `Gemfile.lock` file.
#
- def initialize(root=Dir.pwd,gemfile_lock='Gemfile.lock')
+ # @param [Database] database
+ # The database to scan against.
+ #
+ # @raise [Bundler::GemfileLockNotFound]
+ # The `gemfile_lock` file could not be found within the `root`
+ # directory.
+ #
+ def initialize(root=Dir.pwd,gemfile_lock='Gemfile.lock',database=Database.new,config_dot_file='.bundler-audit.yml')
@root = File.expand_path(root)
- @database = Database.new
- @lockfile = LockfileParser.new(
- File.read(File.join(@root,gemfile_lock))
- )
+ @database = database
+
+ gemfile_lock_path = File.join(@root,gemfile_lock)
+
+ unless File.file?(gemfile_lock_path)
+ raise(Bundler::GemfileLockNotFound,"Could not find #{gemfile_lock.inspect} in #{@root.inspect}")
+ end
+
+ @lockfile = LockfileParser.new(File.read(gemfile_lock_path))
+
+ config_dot_file_full_path = File.join(@root,config_dot_file)
+
+ @config = if File.exist?(config_dot_file_full_path)
+ Configuration.load(config_dot_file_full_path)
+ else
+ Configuration.new
+ end
end
#
+ # Preforms a {#scan} and collects the results into a {Report report}.
+ #
+ # @param [Hash] options
+ # Additional options.
+ #
+ # @option options [Array<String>] :ignore
+ # The advisories to ignore.
+ #
+ # @yield [result]
+ # The given block will be passed the results of the scan.
+ #
+ # @yieldparam [Results::InsecureSource, Results::UnpatchedGem] result
+ # A result from the scan.
+ #
+ # @return [Report]
+ #
+ # @since 0.8.0
+ #
+ def report(options={})
+ report = Report.new()
+
+ scan(options) do |result|
+ report << result
+ yield result if block_given?
+ end
+
+ return report
+ end
+
+ #
# Scans the project for issues.
#
# @param [Hash] options
# Additional options.
#
@@ -57,22 +128,19 @@
# The advisories to ignore.
#
# @yield [result]
# The given block will be passed the results of the scan.
#
- # @yieldparam [InsecureSource, UnpatchedGem] result
+ # @yieldparam [Results::InsecureSource, Results::UnpatchedGem] result
# A result from the scan.
#
# @return [Enumerator]
# If no block is given, an Enumerator will be returned.
#
def scan(options={},&block)
return enum_for(__method__,options) unless block
- ignore = Set[]
- ignore += options[:ignore] if options[:ignore]
-
scan_sources(options,&block)
scan_specs(options,&block)
return self
end
@@ -84,11 +152,11 @@
# Additional options.
#
# @yield [result]
# The given block will be passed the results of the scan.
#
- # @yieldparam [InsecureSource] result
+ # @yieldparam [Results::InsecureSource] result
# A result from the scan.
#
# @return [Enumerator]
# If no block is given, an Enumerator will be returned.
#
@@ -103,17 +171,17 @@
case source
when Source::Git
case source.uri
when /^git:/, /^http:/
unless internal_source?(source.uri)
- yield InsecureSource.new(source.uri)
+ yield Results::InsecureSource.new(source.uri)
end
end
when Source::Rubygems
source.remotes.each do |uri|
if (uri.scheme == 'http' && !internal_source?(uri))
- yield InsecureSource.new(uri.to_s)
+ yield Results::InsecureSource.new(uri.to_s)
end
end
end
end
end
@@ -128,11 +196,11 @@
# The advisories to ignore.
#
# @yield [result]
# The given block will be passed the results of the scan.
#
- # @yieldparam [UnpatchedGem] result
+ # @yieldparam [Results::UnpatchedGem] result
# A result from the scan.
#
# @return [Enumerator]
# If no block is given, an Enumerator will be returned.
#
@@ -141,19 +209,20 @@
# @since 0.4.0
#
def scan_specs(options={})
return enum_for(__method__,options) unless block_given?
- ignore = Set[]
- ignore += options[:ignore] if options[:ignore]
+ ignore = if options[:ignore] then Set.new(options[:ignore])
+ else config.ignore
+ end
@lockfile.specs.each do |gem|
@database.check_gem(gem) do |advisory|
is_ignored = ignore.intersect?(advisory.identifiers.to_set)
next if is_ignored
- yield UnpatchedGem.new(gem,advisory)
+ yield Results::UnpatchedGem.new(gem,advisory)
end
end
end
private
@@ -188,14 +257,18 @@
# List of internal IP address ranges.
#
# @see https://tools.ietf.org/html/rfc1918#section-3
# @see https://tools.ietf.org/html/rfc4193#section-8
+ # @see https://tools.ietf.org/html/rfc6890#section-2.2.2
+ # @see https://tools.ietf.org/html/rfc6890#section-2.2.3
INTERNAL_SUBNETS = %w[
10.0.0.0/8
172.16.0.0/12
192.168.0.0/16
fc00::/7
+ 127.0.0.0/8
+ ::1/128
].map(&IPAddr.method(:new))
#
# Determines whether an IP is internal.
#