Skip to content
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
9 changes: 9 additions & 0 deletions common/lib/dependabot/dependency.rb
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,15 @@ def display_name
display_name_builder.call(name)
end

# Returns all detected versions of the dependency. Only ecosystems that
# support this feature will return more than the current version.
def all_versions
all_versions = metadata[:all_versions]
return [version].compact unless all_versions

all_versions.filter_map(&:version)
end
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👌


def ==(other)
other.instance_of?(self.class) && to_h == other.to_h
end
Expand Down
66 changes: 66 additions & 0 deletions common/spec/dependabot/dependency_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -303,5 +303,71 @@
)
expect(dependency.to_h.keys).not_to include("metadata")
end

it "isn't utilized by the equality operator" do
dependency1 = described_class.new(
name: "dep",
requirements: [],
package_manager: "dummy",
metadata: { foo: 42 }
)
dependency2 = described_class.new(
name: "dep",
requirements: [],
package_manager: "dummy",
metadata: { foo: 43 }
)
expect(dependency1).to eq(dependency2)
end
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good thinking

end

describe "#all_versions" do
it "returns an empty array by default" do
dependency = described_class.new(
name: "dep",
requirements: [],
package_manager: "dummy"
)

expect(dependency.all_versions).to eq([])
end

it "returns the dependency version if all_version metadata isn't present" do
dependency = described_class.new(
name: "dep",
requirements: [],
package_manager: "dummy",
version: "1.0.0"
)

expect(dependency.all_versions).to eq(["1.0.0"])
end

it "returns all_version metadata if present" do
dependency = described_class.new(
name: "dep",
requirements: [],
package_manager: "dummy",
version: "1.0.0",
metadata: {
all_versions: [
described_class.new(
name: "dep",
requirements: [],
package_manager: "dummy",
version: "1.0.0"
),
described_class.new(
name: "dep",
requirements: [],
package_manager: "dummy",
version: "2.0.0"
)
]
}
)

expect(dependency.all_versions).to eq(["1.0.0", "2.0.0"])
end
end
end
7 changes: 2 additions & 5 deletions npm_and_yarn/lib/dependabot/npm_and_yarn/update_checker.rb
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,6 @@ def conflicting_dependencies
dependency: dependency,
target_version: lowest_security_fix_version
)
return conflicts unless defined?(@vulnerability_audit)

vulnerable = [vulnerability_audit].select do |hash|
!hash["fix_available"] && hash["explanation"]
Expand All @@ -144,10 +143,8 @@ def vulnerability_audit
def vulnerable_versions
@vulnerable_versions ||=
begin
all_versions = Set.new([
dependency.version,
*dependency.metadata.fetch(:all_versions, []).filter_map(&:version)
]).filter_map { |v| version_class.new(v) if version_class.correct?(v) }
all_versions = dependency.all_versions.
filter_map { |v| version_class.new(v) if version_class.correct?(v) }

all_versions.select do |v|
security_advisories.any? { |advisory| advisory.vulnerable?(v) }
Expand Down
5 changes: 3 additions & 2 deletions updater/lib/dependabot/job.rb
Original file line number Diff line number Diff line change
Expand Up @@ -112,8 +112,9 @@ def vulnerable?(dependency)
version_class_for_package_manager(dependency.package_manager)
return false unless version_class.correct?(dependency.version)

version = version_class.new(dependency.version)
security_advisories.any? { |a| a.vulnerable?(version) }
all_versions = dependency.all_versions.
filter_map { |v| version_class.new(v) if version_class.correct?(v) }
security_advisories.any? { |a| all_versions.any? { |v| a.vulnerable?(v) } }
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've noticed this code

version_class = Utils.version_class_for_package_manager(package_manager)
version_class.new(version) if version_class.correct?(version)

in a lot of places. Looking at it once again here, I'm wondering if it wouldn't make sense to put this in Dependency. It would be a reasonable argument to say it's too much for that class to know about, but it would remove some boilerplate. Maybe something like this (but probably with better naming)

def correct_version
  version_class = Utils.version_class_for_package_manager(package_manager)
  version_class.new(version) if version_class.correct?(version)
end

def all_correct_versions
  version_class = Utils.version_class_for_package_manager(package_manager)
  all_versions.filter_map { |v| version_class.new(v) if version_class.correct?(v) }
end

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, that's a good thought! There's still some future changes needed to get a successful PR through. If that ends up duplicating this again I think that'd be good opportunity to dry it up?

end

def security_fix?(dependency)
Expand Down
47 changes: 47 additions & 0 deletions updater/spec/dependabot/job_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,53 @@

it { is_expected.to eq(true) }
end

context "for a security fix that doesn't apply" do
let(:security_advisories) do
[
{
"dependency-name" => "business",
"affected-versions" => ["> 1.8.0"],
"patched-versions" => [],
"unaffected-versions" => []
}
]
end

it { is_expected.to eq(false) }
end

context "for a security fix that doesn't apply to some versions" do
let(:security_advisories) do
[
{
"dependency-name" => "business",
"affected-versions" => ["> 1.8.0"],
"patched-versions" => [],
"unaffected-versions" => []
}
]
end

it "should be allowed" do
dependency.metadata[:all_versions] = [
Dependabot::Dependency.new(
name: dependency_name,
package_manager: "bundler",
version: "1.8.0",
requirements: []
),
Dependabot::Dependency.new(
name: dependency_name,
package_manager: "bundler",
version: "1.9.0",
requirements: []
)
]

is_expected.to eq(true)
end
end
end

context "and a dependency whitelist that includes the dependency" do
Expand Down