codeclimate/codeclimate-bundler-audit

View on GitHub
lib/cc/engine/bundler_audit/unpatched_gem_issue.rb

Summary

Maintainability
A
0 mins
Test Coverage
module CC
  module Engine
    module BundlerAudit
      class UnpatchedGemIssue
        CHECK_NAME = "Insecure Dependency".freeze
        GEM_REGEX = /^\s*(?<name>\S+) \([\S.]+\)/
        SEVERITIES = {
          high: "critical",
          medium: "normal",
          low: "info",
        }.freeze

        def initialize(result, gemfile_lock_path, gemfile_lock_lines)
          @gem = result.gem
          @advisory = result.advisory
          @gemfile_lock_path = gemfile_lock_path
          @gemfile_lock_lines = gemfile_lock_lines
        end

        def to_json(*a)
          {
            categories: %w[Security],
            check_name: CHECK_NAME,
            content: {
              body: content_body,
            },
            description: advisory.title,
            location: {
              path: gemfile_lock_path,
              lines: {
                begin: line_number,
                end: line_number,
              },
            },
            remediation_points: remediation_points,
            severity: severity,
            type: "Issue",
            fingerprint: fingerprint,
          }.to_json(a)
        end

        private

        attr_reader :advisory, :gem, :gemfile_lock_path, :gemfile_lock_lines

        def content_body
          lines = ["**Advisory**: #{identifier}"]
          lines << "**Criticality**: #{advisory.criticality.capitalize}" if advisory.criticality
          lines << "**URL**: #{advisory.url}"
          lines << "**Solution**: #{solution}"

          lines.join("\n\n")
        end

        def line_number
          @line_number ||= begin
            gemfile_lock_lines.find_index do |line|
              (match = GEM_REGEX.match(line)) && match[:name] == gem.name
            end + 1
          end
        end

        def remediation_points
          UnpatchedGemRemediation.new(gem.version, parsed_upgrade_versions).points
        end

        def parsed_upgrade_versions
          expanded = advisory.patched_versions.map do |requirement|
            requirement.to_s.split(",").map(&:strip)
          end

          expanded.flatten.map do |version|
            requirements = Gem::Requirement.parse(version)
            requirements.last
          end
        end

        def severity
          SEVERITIES.fetch(advisory.criticality, "normal")
        end

        def solution
          if advisory.patched_versions.any?
            "upgrade to #{advisory.patched_versions.join(', ')}"
          else
            "remove or disable this gem until a patch is available!"
          end
        end

        def identifier
          advisory.cve_id || advisory.osvdb_id
        end

        def fingerprint
          BundlerAudit.fingerprint_for(CHECK_NAME, gem, advisory.id)
        end
      end
    end
  end
end