class Dependabot::UpdateCheckers::Base
def active_advisories
def active_advisories security_advisories.select { |a| a.vulnerable?(T.must(current_version)) } end
def can_compare_requirements?
def can_compare_requirements? (version_from_requirements && latest_version && version_class.correct?(latest_version.to_s)) || false end
def can_update?(requirements_to_unlock:)
def can_update?(requirements_to_unlock:) # Can't update if all versions are being ignored return false if ignore_requirements.include?(requirement_class.new(">= 0")) if dependency.version version_can_update?(requirements_to_unlock: requirements_to_unlock) else # TODO: Handle full unlock updates for dependencies without a lockfile return false if requirements_to_unlock == :none requirements_can_update? end end
def changed_requirements
def changed_requirements (updated_requirements - dependency.requirements) end
def conflicting_dependencies
def conflicting_dependencies [] # return an empty array for ecosystems that don't support this yet end
def current_version
def current_version @current_version ||= T.let( dependency.numeric_version, T.nilable(Dependabot::Version) ) end
def existing_version_is_sha?
def existing_version_is_sha? return false if version_class.correct?(dependency.version) T.must(dependency.version).match?(/^[0-9a-f]{6,}$/) end
def ignore_requirements
def ignore_requirements ignored_versions.flat_map { |req| requirement_class.requirements_array(req) } end
def initialize(
def initialize( dependency:, dependency_files:, credentials:, repo_contents_path: nil, ignored_versions: [], raise_on_ignored: false, security_advisories: [], requirements_update_strategy: nil, dependency_group: nil, update_cooldown: nil, options: {} ) @dependency = dependency @dependency_files = dependency_files @repo_contents_path = repo_contents_path @credentials = credentials @requirements_update_strategy = requirements_update_strategy @ignored_versions = ignored_versions @raise_on_ignored = raise_on_ignored @security_advisories = security_advisories @dependency_group = dependency_group @update_cooldown = update_cooldown @options = options end
def latest_resolvable_previous_version(_updated_version)
def latest_resolvable_previous_version(_updated_version) dependency.version end
def latest_resolvable_version
def latest_resolvable_version raise NotImplementedError, "#{self.class} must implement #latest_resolvable_version" end
def latest_resolvable_version_with_no_unlock
def latest_resolvable_version_with_no_unlock raise NotImplementedError, "#{self.class} must implement #latest_resolvable_version_with_no_unlock" end
def latest_version
def latest_version raise NotImplementedError, "#{self.class} must implement #latest_version" end
def latest_version_resolvable_with_full_unlock?
def latest_version_resolvable_with_full_unlock? raise NotImplementedError, "#{self.class} must implement #latest_version_resolvable_with_full_unlock?" end
def lowest_resolvable_security_fix_version
def lowest_resolvable_security_fix_version raise NotImplementedError, "#{self.class} must implement #lowest_resolvable_security_fix_version" end
def lowest_security_fix_version
def lowest_security_fix_version raise NotImplementedError, "#{self.class} must implement #lowest_security_fix_version" end
def numeric_version_can_update?(requirements_to_unlock:)
def numeric_version_can_update?(requirements_to_unlock:) return false if numeric_version_up_to_date? case requirements_to_unlock&.to_sym when :none new_version = latest_resolvable_version_with_no_unlock return false unless new_version new_version > current_version when :own preferred_version_resolvable_with_unlock? when :all latest_version_resolvable_with_full_unlock? else raise "Unknown unlock level '#{requirements_to_unlock}'" end end
def numeric_version_up_to_date?
def numeric_version_up_to_date? return false unless latest_version # If a lockfile isn't out of date and the package has switched to a git # source then we'll get a numeric version switching to a git SHA. In # this case we treat the version as up-to-date so that it's ignored. return true if latest_version.to_s.match?(/^[0-9a-f]{40}$/) T.must(latest_version) <= current_version end
def preferred_resolvable_version
def preferred_resolvable_version # If this dependency is vulnerable, prefer trying to update to the # lowest_resolvable_security_fix_version. Otherwise update all the way # to the latest_resolvable_version. return lowest_resolvable_security_fix_version if vulnerable? latest_resolvable_version rescue NotImplementedError latest_resolvable_version end
def preferred_version_resolvable_with_unlock?
def preferred_version_resolvable_with_unlock? new_version = preferred_resolvable_version return false unless new_version if existing_version_is_sha? return false if new_version.to_s.start_with?(T.must(dependency.version)) elsif new_version <= current_version return false end updated_requirements.none? { |r| r[:requirement] == :unfixable } end
def requirement_class
def requirement_class dependency.requirement_class end
def requirements_can_update?
def requirements_can_update? return false if changed_requirements.none? changed_requirements.none? { |r| r[:requirement] == :unfixable } end
def requirements_unlocked_or_can_be?
def requirements_unlocked_or_can_be? true end
def requirements_up_to_date?
def requirements_up_to_date? return T.must(version_from_requirements) >= version_class.new(latest_version.to_s) if can_compare_requirements? changed_requirements.none? end
def sha1_version_can_update?(requirements_to_unlock:)
def sha1_version_can_update?(requirements_to_unlock:) return false if sha1_version_up_to_date? # All we can do with SHA-1 hashes is check for presence and equality case requirements_to_unlock&.to_sym when :none new_version = latest_resolvable_version_with_no_unlock !new_version&.to_s&.start_with?(T.must(dependency.version)) when :own preferred_version_resolvable_with_unlock? when :all latest_version_resolvable_with_full_unlock? else raise "Unknown unlock level '#{requirements_to_unlock}'" end end
def sha1_version_up_to_date?
def sha1_version_up_to_date? latest_version&.to_s&.start_with?(T.must(dependency.version)) || false end
def up_to_date?
def up_to_date? if dependency.version version_up_to_date? else requirements_up_to_date? end end
def updated_dependencies(requirements_to_unlock:)
def updated_dependencies(requirements_to_unlock:) return [] unless can_update?(requirements_to_unlock: requirements_to_unlock) case requirements_to_unlock&.to_sym when :none then [updated_dependency_without_unlock] when :own then [updated_dependency_with_own_req_unlock] when :all then updated_dependencies_after_full_unlock else raise "Unknown unlock level '#{requirements_to_unlock}'" end end
def updated_dependencies_after_full_unlock
def updated_dependencies_after_full_unlock raise NotImplementedError end
def updated_dependency_with_own_req_unlock
def updated_dependency_with_own_req_unlock version = preferred_resolvable_version.to_s previous_version = latest_resolvable_previous_version(version) Dependency.new( name: dependency.name, version: version, requirements: updated_requirements, previous_version: previous_version, previous_requirements: dependency.requirements, package_manager: dependency.package_manager, metadata: dependency.metadata, subdependency_metadata: dependency.subdependency_metadata ) end
def updated_dependency_without_unlock
def updated_dependency_without_unlock version = latest_resolvable_version_with_no_unlock.to_s previous_version = latest_resolvable_previous_version(version) Dependency.new( name: dependency.name, version: version, requirements: dependency.requirements, previous_version: previous_version, previous_requirements: dependency.requirements, package_manager: dependency.package_manager, metadata: dependency.metadata, subdependency_metadata: dependency.subdependency_metadata ) end
def updated_requirements
def updated_requirements raise NotImplementedError end
def version_can_update?(requirements_to_unlock:)
def version_can_update?(requirements_to_unlock:) if existing_version_is_sha? return sha1_version_can_update?( requirements_to_unlock: requirements_to_unlock ) end numeric_version_can_update?( requirements_to_unlock: requirements_to_unlock ) end
def version_class
def version_class dependency.version_class end
def version_from_requirements
def version_from_requirements @version_from_requirements ||= T.let( dependency.requirements.filter_map { |r| r.fetch(:requirement) } .flat_map { |req_str| requirement_class.requirements_array(req_str) } .flat_map(&:requirements) .reject { |req_array| req_array.first.start_with?("<") } .map(&:last) .max, T.nilable(T.any(String, Gem::Version)) ) end
def version_up_to_date?
def version_up_to_date? return sha1_version_up_to_date? if existing_version_is_sha? numeric_version_up_to_date? end
def vulnerable?
def vulnerable? return false if security_advisories.none? # Can't (currently) detect whether dependencies without a version # (i.e., for repos without a lockfile) are vulnerable return false unless dependency.version # Can't (currently) detect whether git dependencies are vulnerable return false if existing_version_is_sha? active_advisories.any? end