Skip to content
This repository has been archived by the owner on Jul 24, 2021. It is now read-only.

CLI: Adding Config File Parser #17

Merged
merged 2 commits into from
Oct 19, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -112,3 +112,5 @@ build-iPhoneSimulator/
*.iml

# End of https://www.gitignore.io/api/jetbrains,ruby

.depspy.yml
12 changes: 12 additions & 0 deletions example.depspy.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
path: '/path/to/files' # Path to find files. DEFAULT: Dir.pwd
files: 'comma.sep,file.list' # Specific file list relative to `path`. DEFAULT: All files
formatter: 'text' # Output format. DEFAULT: text; AVAILABLE: text,json,yaml
platform: 'rubygems' # Supported YAVDB package manager lookup. DEFAULT: not specified (ALL); AVAILABLE: (See: https://github.com/rtfpessoa/yavdb/blob/master/lib/yavdb/constants.rb#L31)
output-path: '/path/to/output' # Path to generate report to. DEFAULT: not specified (console output)
database-path: '/path/to/yavdb/database' # Path to find/store local YAVDB DB. DEFAULT: YAVDB::Constants::DEFAULT_YAVDB_DATABASE_PATH (See: https://github.com/rtfpessoa/yavdb/blob/master/lib/yavdb/constants.rb#L28)
offline: false # Operate in offline mode (don't try to get YAVDB). Must have local YAVDB available. DEFAULT: false; AVAILABLE: true,false
severity-threshold: 'low' # Threshold for non-zero exit status. Doesn't change output. DEFAULT: 'low'; AVAILABLE: (See: https://github.com/rtfpessoa/yavdb/blob/master/lib/yavdb/constants.rb#L33)
with-color: true # Generate colored console output. DEFAULT: true; AVAILABLE: true,false
ignore: # A list of all YAVDB vulnerability identifiers to ignore. Removes from output.
- "identifier:to:ignore:19551105"
vuln-db-path: '/path/to/yavdb' # Path to local YAVDB for updating. DEFAULT: YAVDB::Constants::DEFAULT_YAVDB_PATH (See: https://github.com/rtfpessoa/yavdb/blob/master/lib/yavdb/constants.rb#L27)
67 changes: 48 additions & 19 deletions lib/dependency_spy/cli.rb
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
require_relative 'outputs/stdout'
require_relative 'outputs/file'
require_relative 'helper/helper'
require_relative 'helper/config_file'

module DependencySpy
class CLI < Thor
Expand All @@ -37,35 +38,49 @@ class CLI < Thor
DependencySpy::Formatters::Yaml
]

class_option('verbose', :type => :boolean, :default => false)
class_option('verbose', :type => :boolean)

desc('check', 'Check dependencies for known vulnerabilities')
method_option('path', :aliases => :p, :type => :string, :default => Dir.pwd)
method_option('config-file-path', :aliases => :c, :type => :string)
method_option('path', :aliases => :p, :type => :string)
method_option('files', :type => :string)
method_option('formatter', :aliases => :f, :type => :string, :enum => FORMATTERS.map { |f| f.name.split('::').last.downcase }, :default => FORMATTERS.first.name.split('::').last.downcase)
method_option('formatter', :aliases => :f, :type => :string, :enum => FORMATTERS.map { |f| f.name.split('::').last.downcase })
method_option('platform', :aliases => :m, :type => :string, :enum => YAVDB::Constants::POSSIBLE_PACKAGE_MANAGERS.map(&:downcase))
method_option('output-path', :aliases => :o, :type => :string)
method_option('database-path', :type => :string, :aliases => :p, :default => YAVDB::Constants::DEFAULT_YAVDB_DATABASE_PATH)
method_option('offline', :type => :boolean, :default => false)
method_option('severity-threshold', :aliases => :s, :type => :string, :enum => YAVDB::Constants::SEVERITIES, :default => 'low')
method_option('with-color', :type => :boolean, :default => true)
method_option('ignore', :aliases => :i, :type => :array, :default => [])
method_option('database-path', :type => :string, :aliases => :p)
method_option('offline', :type => :boolean)
method_option('severity-threshold', :aliases => :s, :type => :string, :enum => YAVDB::Constants::SEVERITIES)
method_option('with-color', :type => :boolean)
method_option('ignore', :aliases => :i, :type => :array)
def check
the_options = options.dup
the_options[:database_path] = the_options[:"database-path"]
defaults = {
'verbose' => false,
'path' => Dir.pwd,
'formatter' => FORMATTERS.first.name.split('::').last.downcase,
'database-path' => YAVDB::Constants::DEFAULT_YAVDB_DATABASE_PATH,
'offline' => false,
'severity-threshold' => 'low',
'with-color' => true,
'ignore' => []
}
the_options = defaults.merge(options)

api_options = the_options.transform_keys(&:to_sym)
api_options[:database_path] = api_options[:'database-path']
the_options.freeze
manifests = API.check(the_options)
api_options.freeze
manifests = API.check(api_options)

formatted_output = if (options['formatter'] == 'text') && !options['output-path'] && options['with-color']
DependencySpy::Formatters::Text.format(manifests, options['severity-threshold'])
formatted_output = if (the_options['formatter'] == 'text') && !the_options['output-path'] && the_options['with-color']
DependencySpy::Formatters::Text.format(manifests, the_options['severity-threshold'])
else
FORMATTERS
.find { |f| f.name.split('::').last.downcase == options['formatter'] }
.find { |f| f.name.split('::').last.downcase == the_options['formatter'] }
.format(manifests)
end

if options['output-path']
DependencySpy::Outputs::FileSystem.write(options['output-path'], formatted_output)
if the_options['output-path']
DependencySpy::Outputs::FileSystem.write(the_options['output-path'], formatted_output)
else
DependencySpy::Outputs::StdOut.write(formatted_output)
end
Expand All @@ -74,19 +89,33 @@ def check
manifests.any? do |manifest|
manifest[:dependencies]&.any? do |dependency|
dependency[:vulnerabilities]&.any? do |vuln|
DependencySpy::Helper.severity_above_threshold?(vuln.severity, options['severity-threshold'])
DependencySpy::Helper.severity_above_threshold?(vuln.severity, the_options['severity-threshold'])
end
end
end

exit(1) if has_vulnerabilities
end

method_option('vuln-db-path', :aliases => :d, :type => :string, :default => YAVDB::Constants::DEFAULT_YAVDB_PATH)
method_option('vuln-db-path', :aliases => :d, :type => :string)
desc('update', 'Download or update database from the official yavdb repository.')

def update
API.update(options['vuln-db-path'])
defaults = {
'verbose' => false,
'vuln-db-path' => YAVDB::Constants::DEFAULT_YAVDB_PATH
}
the_options = defaults.merge(options)
the_options.freeze
API.update(the_options['vuln-db-path'])
end

private

def options
cli_options = super
config_file_options = DependencySpy::ConfigFile.get_config(cli_options[:'config-file-path'])
config_file_options.merge(cli_options)
end

end
Expand Down
41 changes: 41 additions & 0 deletions lib/dependency_spy/helper/config_file.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
require 'yaml'

module DependencySpy
class ConfigFile

SAFE_CONFIG_PARAMS = [
'path',
'files',
'formatter',
'platform',
'output-path',
'database-path',
'offline',
'severity-threshold',
'with-color',
'ignore',
'vuln-db-path'
].freeze

def self.get_config(config_file_path = nil)
if !config_file_path.nil? && !File.file?(config_file_path)
puts 'Config file specified but not found.'
exit(10)

end

begin
file_path = config_file_path || '.depspy.yml'
config = YAML.load_file(file_path) || {}
config.slice(*SAFE_CONFIG_PARAMS)
rescue Errno::ENOENT
{}
rescue Psych::SyntaxError => e
puts 'Config File Parsing Error:'
puts e.message
exit(10)
end
end

end
end
59 changes: 59 additions & 0 deletions spec/dependency_spy_cli_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# dependency_spy - Finds known vulnerabilities in your dependencies
# Copyright (C) 2017-2018 Rodrigo Fernandes
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program 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 Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.

require 'spec_helper'

RSpec::Matchers.define :superset_of do |desired|
match { |actual| desired.map { |k, v| actual[k] == v }.all? }
end

RSpec.describe DependencySpy::CLI do
describe 'check' do
it 'should call API with defaults given no config file' do
api_call_defaults = {
:verbose => false,
:path => Dir.pwd,
:formatter => 'text',
:database_path => YAVDB::Constants::DEFAULT_YAVDB_DATABASE_PATH,
:offline => false,
:ignore => []
}
expect(DependencySpy::API).to receive(:check).with(superset_of(api_call_defaults)).and_return([])
DependencySpy::CLI.new.check
end

it 'should call API with overridden given a config file' do
api_call_args = {
:formatter => 'json'
}
expect(DependencySpy::ConfigFile).to receive(:get_config).and_return('formatter' => 'json')
expect(DependencySpy::API).to receive(:check).with(superset_of(api_call_args)).and_return([])
DependencySpy::CLI.new.check
end

it 'should call API with overridden given a config file and command line override' do
api_call_args = {
:formatter => 'json',
:offline => false
}
# allow(Thor).to receive(:options){{ 'offline' => false }}
cli = DependencySpy::CLI.new([], 'offline' => false)
expect(DependencySpy::ConfigFile).to receive(:get_config).and_return('formatter' => 'json', 'offline' => true)
expect(DependencySpy::API).to receive(:check).with(superset_of(api_call_args)).and_return([])
cli.check
end
end
end
1 change: 1 addition & 0 deletions spec/spec_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
SimpleCov.start

require 'dependency_spy'
require 'dependency_spy/cli'

RSpec.configure do |config|
# Enable flags like --only-failures and --next-failure
Expand Down