-
Notifications
You must be signed in to change notification settings - Fork 702
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
Adding interface, implementation, and tests for vulnerability capabilities for a package domain model #6279
base: dev-feature-PackageDomainModel
Are you sure you want to change the base?
Changes from all commits
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,20 @@ | ||
// Copyright (c) .NET Foundation. All rights reserved. | ||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. | ||
|
||
#nullable enable | ||
|
||
using System.Collections.Generic; | ||
using NuGet.Protocol; | ||
using NuGet.VisualStudio.Internal.Contracts; | ||
|
||
namespace NuGet.PackageManagement.UI | ||
{ | ||
interface IVulnerable | ||
{ | ||
public IEnumerable<PackageVulnerabilityMetadataContextInfo> Vulnerabilities { get; } | ||
|
||
public bool IsVulnerable { get; } | ||
|
||
public PackageVulnerabilitySeverity VulnerabilityMaxSeverity { get; } | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
// Copyright (c) .NET Foundation. All rights reserved. | ||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. | ||
|
||
#nullable enable | ||
|
||
using System; | ||
using System.Collections.Generic; | ||
using System.Linq; | ||
using NuGet.Protocol; | ||
using NuGet.VisualStudio.Internal.Contracts; | ||
|
||
namespace NuGet.PackageManagement.UI | ||
{ | ||
public class VulnerableCapability : IVulnerable | ||
{ | ||
private IEnumerable<PackageVulnerabilityMetadataContextInfo> _vulnerabilities = []; | ||
|
||
public IEnumerable<PackageVulnerabilityMetadataContextInfo> Vulnerabilities | ||
{ | ||
get => _vulnerabilities; | ||
private set => _vulnerabilities = value.OrderByDescending(v => v?.Severity ?? -1); | ||
} | ||
|
||
public bool IsVulnerable => Vulnerabilities.Any(v => v != null); | ||
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. so you're saying it's possible to have a list of vulnerabilities where all entries are null? 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. It is, programmatically. It doesn't make much sense to have that, but there a few strategies I can think of:
Can you think of another way you'd suggest instead? 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. Yea, I think iterating through them to validate there aren't any nulls seems excessive. I guess I didn't realize we had been accounting for this in the old code too. 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. @jgonz120 We aren't accounting for it explicitly in the ViewModels today which presents a potential flaw. The goal here is to make this code more robust than what we had before and put it under test. By handling this edge case gracefully, we avoid any NRE's in case a null makes its way into the list at any point. 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. This file has 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.
I see what you mean. Both the tests and the 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. Make this class immutable and avoid paid the cost of 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. Additionally, the current implementation of calculating the Referring to Nikolche's other comment about using a concrete class, all these heap allocations could be avoided by using a concrete class, and making this property's body 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.
This is not correct. The OrderByDescending is in the setter of the Vulnerabilities property, not in the getter. Therefore, it will only be sorted once when assigned privately, not upon each access. |
||
|
||
public PackageVulnerabilitySeverity VulnerabilityMaxSeverity | ||
{ | ||
get | ||
{ | ||
// Vulnerabilities are ordered so the first element is always the highest severity | ||
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. This is great for performance, not sure if we are currently iterating. More context: https://learn.microsoft.com/en-us/nuget/api/vulnerability-info#vulnerability-page. 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. If this is not officially part of the contract, we shouldn't depend on it. 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. Correct, there's no way to guarantee the list will be ordered from any and all vulnerability sources, both current and future. This comment points out that the list is ordered. It's ordered in this class (see the setter) explicitly to avoid making assumptions about the data source. Does that help? Do you have any suggested edits? 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. It does, right? 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. Doesn't that talk about the way the version ranges are sorted and not the list of vulnerabilities? |
||
int? severity = Vulnerabilities.FirstOrDefault()?.Severity; | ||
if (severity != null && Enum.IsDefined(typeof(PackageVulnerabilitySeverity), severity)) | ||
{ | ||
return (PackageVulnerabilitySeverity)severity; | ||
} | ||
else | ||
{ | ||
return PackageVulnerabilitySeverity.Unknown; | ||
} | ||
} | ||
} | ||
|
||
public VulnerableCapability(IEnumerable<PackageVulnerabilityMetadataContextInfo> vulnerabilities) | ||
{ | ||
Vulnerabilities = vulnerabilities ?? throw new ArgumentNullException(nameof(vulnerabilities)); | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,92 @@ | ||
// Copyright (c) .NET Foundation. All rights reserved. | ||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. | ||
|
||
#nullable enable | ||
|
||
using System; | ||
using System.Collections.Generic; | ||
using System.Linq; | ||
using NuGet.Protocol; | ||
using NuGet.VisualStudio.Internal.Contracts; | ||
using Xunit; | ||
|
||
namespace NuGet.PackageManagement.UI.Test | ||
{ | ||
public class VulnerableCapabilityTests | ||
{ | ||
[Theory] | ||
[InlineData(1, true)] | ||
[InlineData(0, false)] | ||
public void IsVulnerable_VariousVulnerabilities_ReturnsExpected(int severity, bool expected) | ||
{ | ||
// Arrange | ||
IEnumerable<PackageVulnerabilityMetadataContextInfo> vulnerabilities = severity > 0 | ||
? [new(new Uri("http://example.com"), severity)] | ||
: []; | ||
var vulnerableCapability = new VulnerableCapability(vulnerabilities); | ||
|
||
// Act | ||
var result = vulnerableCapability.IsVulnerable; | ||
|
||
// Assert | ||
Assert.Equal(expected, result); | ||
} | ||
|
||
[Fact] | ||
public void IsVulnerable_NullVulnerabilityInCollection_ReturnsFalse() | ||
{ | ||
// Arrange | ||
List<PackageVulnerabilityMetadataContextInfo> vulnerabilities = [null]; | ||
var vulnerableCapability = new VulnerableCapability(vulnerabilities); | ||
|
||
// Act | ||
var result = vulnerableCapability.IsVulnerable; | ||
|
||
// Assert | ||
Assert.False(result); | ||
} | ||
|
||
[Theory] | ||
[InlineData(new int[] { (int)PackageVulnerabilitySeverity.Low, (int)PackageVulnerabilitySeverity.High }, PackageVulnerabilitySeverity.High)] | ||
[InlineData(new int[] { (int)PackageVulnerabilitySeverity.Critical, (int)PackageVulnerabilitySeverity.Unknown }, PackageVulnerabilitySeverity.Critical)] | ||
[InlineData(new int[] { }, PackageVulnerabilitySeverity.Unknown)] | ||
[InlineData(new int[] { -2 }, PackageVulnerabilitySeverity.Unknown)] | ||
public void VulnerabilityMaxSeverity_VariousVulnerabilities_ReturnsExpected(int[] severities, PackageVulnerabilitySeverity expected) | ||
{ | ||
// Arrange | ||
var vulnerabilities = severities.Select(severity => new PackageVulnerabilityMetadataContextInfo(new Uri("http://example.com"), severity)).ToList(); | ||
var vulnerableCapability = new VulnerableCapability(vulnerabilities); | ||
|
||
// Act | ||
var result = vulnerableCapability.VulnerabilityMaxSeverity; | ||
|
||
// Assert | ||
Assert.Equal(expected, result); | ||
} | ||
|
||
[Fact] | ||
public void Constructor_OrdersVulnerabilitiesBySeverity_DescendingOrder() | ||
{ | ||
// Arrange | ||
var vulnerabilities = new List<PackageVulnerabilityMetadataContextInfo> | ||
{ | ||
new(new Uri("http://example.com/high"), (int)PackageVulnerabilitySeverity.High), | ||
new(new Uri("http://example.com/low"), (int)PackageVulnerabilitySeverity.Low), | ||
new(new Uri("http://example.com/moderate"), (int)PackageVulnerabilitySeverity.Moderate), | ||
new(new Uri("http://example.com/critical"), (int)PackageVulnerabilitySeverity.Critical) | ||
}; | ||
|
||
// Act | ||
var vulnerableCapability = new VulnerableCapability(vulnerabilities); | ||
var orderedVulnerabilities = vulnerableCapability.Vulnerabilities.ToList(); | ||
|
||
// Assert | ||
Assert.Collection(orderedVulnerabilities, | ||
item => Assert.Equal((int)PackageVulnerabilitySeverity.Critical, item.Severity), | ||
item => Assert.Equal((int)PackageVulnerabilitySeverity.High, item.Severity), | ||
item => Assert.Equal((int)PackageVulnerabilitySeverity.Moderate, item.Severity), | ||
item => Assert.Equal((int)PackageVulnerabilitySeverity.Low, item.Severity) | ||
); | ||
} | ||
} | ||
} |
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.
Can you use more concrete types to avoid paying the enumerator costs?