-
Notifications
You must be signed in to change notification settings - Fork 0
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Refactor code #4
Changes from all commits
806339d
5209f46
b086a17
a8c45c3
f80e860
417e249
74f07eb
a6a5f60
ba9b1bd
1ebee42
fe5a1d9
701af66
ed04b8e
a6a0311
fe0fc72
f624da2
2f340b9
80e78f0
a9be82a
224d07c
4b3a807
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
# frozen_string_literal: true | ||
|
||
module Polariscope | ||
module Scanner | ||
class AdvisoriesHealthScore | ||
def initialize(dependency_context, calculation_context) | ||
@dependency_context = dependency_context | ||
@calculation_context = calculation_context | ||
end | ||
|
||
def health_score | ||
(1 + advisories_penalty)**-Math.log(calculation_context.advisory_severity) | ||
end | ||
|
||
private | ||
|
||
attr_reader :dependency_context | ||
attr_reader :calculation_context | ||
|
||
def advisories_penalty | ||
dependency_context | ||
.advisories | ||
.map(&:criticality) | ||
.sum { |criticality| calculation_context.advisory_penalty_for(criticality) } | ||
end | ||
end | ||
end | ||
end |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
# frozen_string_literal: true | ||
|
||
require 'bundler/audit/database' | ||
|
||
module Polariscope | ||
module Scanner | ||
module AuditDatabase | ||
extend self | ||
|
||
ONE_DAY = 24 * 60 * 60 | ||
|
||
def update_if_necessary | ||
update_audit_database! if database_outdated? | ||
end | ||
|
||
private | ||
|
||
def update_audit_database! | ||
Bundler::Audit::Database.update!(quiet: true) | ||
end | ||
|
||
def database_outdated? | ||
audit_db_missing? || audit_db_stale? | ||
end | ||
|
||
def audit_db_missing? | ||
!Bundler::Audit::Database.exists? | ||
end | ||
|
||
def audit_db_stale? | ||
((Time.now - Bundler::Audit::Database.new.last_updated_at) / ONE_DAY) > 1.0 | ||
end | ||
Comment on lines
+30
to
+32
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The logic was changed for this one to update the DB if it's stale more than one day instead of previously more than seven. |
||
end | ||
end | ||
end |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
# frozen_string_literal: true | ||
|
||
module Polariscope | ||
module Scanner | ||
class CalculationContext | ||
DEPENDENCY_PRIORITIES = { rails: 10.0 }.freeze | ||
GROUP_PRIORITIES = { default: 2.0, production: 2.0 }.freeze | ||
DEFAULT_DEPENDENCY_PRIORITY = 1.0 | ||
|
||
ADVISORY_SEVERITY = 1.09 | ||
ADVISORY_PENALTIES = { | ||
none: 0.0, | ||
low: 0.5, | ||
medium: 1.0, | ||
high: 3.0, | ||
critical: 5.0 | ||
}.freeze | ||
FALLBACK_ADVISORY_PENALTY = 0.5 | ||
|
||
MAJOR_VERSION_PENALTY = 1 | ||
NEW_VERSIONS_SEVERITY = 1.07 | ||
SEGMENT_SEVERITIES = [1.7, 1.15, 1.01].freeze | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Previously, this constant had values Because of that, and the fact that Semver doesn't define a fourth segment and its meaning is defined by dependency owners (like Rails, which uses it for security releases), I've decided to remove it. In the case of Rails, health score will still be impacted by advisories because v7.0.0.1 will have an advisory that v7.0.0.2 won't. |
||
FALLBACK_SEGMENT_SEVERITY = 1.0 | ||
|
||
def initialize(**opts) | ||
@dependency_priorities = opts.fetch(:dependency_priorities, DEPENDENCY_PRIORITIES) | ||
@group_priorities = opts.fetch(:group_priorities, GROUP_PRIORITIES) | ||
@default_dependency_priority = opts.fetch(:default_dependency_priority, DEFAULT_DEPENDENCY_PRIORITY) | ||
|
||
@advisory_severity = opts.fetch(:advisory_severity, ADVISORY_SEVERITY) | ||
@advisory_penalties = opts.fetch(:advisory_penalties, ADVISORY_PENALTIES) | ||
@fallback_advisory_penalty = opts.fetch(:fallback_advisory_penalty, FALLBACK_ADVISORY_PENALTY) | ||
|
||
@major_version_penalty = opts.fetch(:major_version_penalty, MAJOR_VERSION_PENALTY) | ||
@new_versions_severity = opts.fetch(:new_versions_severity, NEW_VERSIONS_SEVERITY) | ||
@segment_severities = opts.fetch(:segment_severities, SEGMENT_SEVERITIES) | ||
@fallback_segment_severity = opts.fetch(:fallback_segment_severity, FALLBACK_SEGMENT_SEVERITY) | ||
end | ||
|
||
def priority_for(dependency) | ||
dependency_priorities[dependency.name.to_sym] || | ||
group_priorities[dependency.groups.first] || | ||
default_dependency_priority | ||
end | ||
|
||
def advisory_penalty_for(criticality) | ||
advisory_penalties.fetch(criticality, fallback_advisory_penalty) | ||
end | ||
|
||
def segment_severity(segment) | ||
return 1.0 unless segment | ||
|
||
segment_severities.fetch(segment, fallback_segment_severity) | ||
end | ||
|
||
attr_reader :advisory_severity | ||
attr_reader :new_versions_severity | ||
attr_reader :major_version_penalty | ||
|
||
private | ||
|
||
attr_reader :dependency_priorities | ||
attr_reader :default_dependency_priority | ||
attr_reader :group_priorities | ||
attr_reader :advisory_penalties | ||
attr_reader :fallback_advisory_penalty | ||
attr_reader :segment_severities | ||
attr_reader :fallback_segment_severity | ||
end | ||
end | ||
end |
This file was deleted.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,105 @@ | ||
# frozen_string_literal: true | ||
|
||
require_relative 'ruby_scanner' | ||
|
||
require 'tempfile' | ||
require 'bundler/audit/configuration' | ||
|
||
module Polariscope | ||
module Scanner | ||
class DependencyContext | ||
DEFAULT_SPEC_TYPE = :released | ||
|
||
def initialize(**opts) | ||
@gemfile_content = opts.fetch(:gemfile_content, nil) | ||
@gemfile_lock_content = opts.fetch(:gemfile_lock_content, nil) | ||
@bundler_audit_config_content = opts.fetch(:bundler_audit_config_content, '') | ||
@spec_type = opts.fetch(:spec_type, DEFAULT_SPEC_TYPE) | ||
end | ||
|
||
def no_dependencies? | ||
blank_value?(gemfile_content) || blank_value?(gemfile_lock_content) || dependencies.empty? | ||
end | ||
|
||
def dependencies | ||
bundle_definition.dependencies | ||
end | ||
|
||
def dependency_versions(dependency) | ||
[current_dependency_version(dependency), gem_versions.versions_for(dependency.name)] | ||
end | ||
|
||
def advisories | ||
specs | ||
.flat_map { |gem| audit_database.check_gem(gem).to_a } | ||
.concat(ruby_scanner.vulnerable_advisories) | ||
.reject { |advisory| ignored_advisories.intersect?(advisory.identifiers.to_set) } | ||
end | ||
|
||
private | ||
|
||
attr_reader :gemfile_content | ||
attr_reader :gemfile_lock_content | ||
attr_reader :bundler_audit_config_content | ||
attr_reader :spec_type | ||
|
||
def ruby_scanner | ||
@ruby_scanner ||= RubyScanner.new(bundle_definition.locked_ruby_version_object) | ||
end | ||
|
||
def gem_versions | ||
@gem_versions ||= GemVersions.new(dependencies.map(&:name), spec_type: spec_type) | ||
end | ||
|
||
def bundle_definition | ||
@bundle_definition ||= | ||
::Tempfile.create do |gemfile| | ||
::Tempfile.create do |gemfile_lock| | ||
gemfile.puts parseable_gemfile_content | ||
gemfile.rewind | ||
|
||
gemfile_lock.puts gemfile_lock_content | ||
gemfile_lock.rewind | ||
|
||
Bundler::Definition.build(gemfile.path, gemfile_lock.path, false) | ||
end | ||
end | ||
end | ||
|
||
def current_dependency_version(dependency) | ||
specs.find { |spec| dependency.name == spec.name }.version | ||
end | ||
|
||
def specs | ||
bundle_definition.locked_gems.specs | ||
end | ||
|
||
def ignored_advisories | ||
audit_configuration.ignore | ||
end | ||
|
||
def audit_configuration | ||
@audit_configuration ||= Tempfile.create do |file| | ||
file.puts bundler_audit_config_content | ||
file.rewind | ||
|
||
Bundler::Audit::Configuration.load(file.path) | ||
rescue StandardError | ||
Bundler::Audit::Configuration.new | ||
end | ||
end | ||
|
||
def audit_database | ||
@audit_database ||= Bundler::Audit::Database.new | ||
end | ||
|
||
def parseable_gemfile_content | ||
gemfile_content.gsub("gemspec\n", '').gsub(/^ruby.*$\R/, '') | ||
end | ||
|
||
def blank_value?(value) | ||
value.nil? || value.empty? | ||
end | ||
end | ||
end | ||
end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This change will enable us to support command-line arguments for all configuration options later on.