Skip to content

Commit 5ce2f98

Browse files
authored
Add vulnerability messaging to package details page (#8358)
Surface vulnerabilities in package detail page
1 parent 86af6b6 commit 5ce2f98

10 files changed

Lines changed: 493 additions & 150 deletions

File tree

src/Bootstrap/dist/css/bootstrap-theme.css

Lines changed: 42 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/Bootstrap/less/theme/page-display-package.less

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,50 @@
6363
}
6464
}
6565

66+
.vulnerabilities-container {
67+
.vulnerabilities-expander {
68+
display: flex;
69+
justify-content: space-between;
70+
vertical-align: middle;
71+
width: 100%;
72+
73+
.vulnerabilities-expander-container {
74+
display: flex;
75+
76+
.vulnerabilities-expander-icon {
77+
position: unset;
78+
top: unset;
79+
}
80+
81+
.vulnerabilities-expander-info-right {
82+
padding-left: 15px;
83+
}
84+
}
85+
}
86+
87+
.vulnerabilities-content-container {
88+
margin-top: 15px;
89+
padding-top: 15px;
90+
border-top: 1px solid lightgray;
91+
92+
.vulnerabilities-list {
93+
border-collapse: unset;
94+
}
95+
}
96+
97+
.vulnerabilities-severity-critical {
98+
color: red
99+
}
100+
101+
.vulnerabilities-severity-high {
102+
color: red
103+
}
104+
105+
.vulnerabilities-severity-moderate {
106+
color: blue
107+
}
108+
}
109+
66110
.failed-validation-alert-list {
67111
margin-top: 15px;
68112
margin-bottom: 15px;

src/NuGetGallery/Controllers/PackagesController.cs

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -873,12 +873,18 @@ public virtual async Task<ActionResult> DisplayPackage(string id, string version
873873
}
874874

875875
var readme = await _readMeService.GetReadMeHtmlAsync(package);
876-
var deprecations = _deprecationService.GetDeprecationsById(id);
877-
var packageKeyToDeprecation = deprecations
878-
.GroupBy(d => d.PackageKey)
879-
.ToDictionary(g => g.Key, g => g.First());
880876

881-
var packageKeyToVulnerabilities = _vulnerabilitiesService.GetVulnerabilitiesById(id);
877+
var isPackageDeprecationEnabled = _featureFlagService.IsManageDeprecationEnabled(currentUser, allVersions);
878+
var packageKeyToDeprecation = isPackageDeprecationEnabled
879+
? _deprecationService.GetDeprecationsById(id)
880+
.GroupBy(d => d.PackageKey)
881+
.ToDictionary(g => g.Key, g => g.First())
882+
: null;
883+
884+
var isPackageVulnerabilitiesEnabled = _featureFlagService.IsDisplayVulnerabilitiesEnabled();
885+
var packageKeyToVulnerabilities = isPackageVulnerabilitiesEnabled
886+
? _vulnerabilitiesService.GetVulnerabilitiesById(id)
887+
: null;
882888

883889
IReadOnlyList<PackageRename> packageRenames = null;
884890
if (_featureFlagService.IsPackageRenamesEnabled(currentUser))
@@ -900,8 +906,8 @@ public virtual async Task<ActionResult> DisplayPackage(string id, string version
900906
model.SymbolsPackageValidationIssues = _validationService.GetLatestPackageValidationIssues(model.LatestSymbolsPackage);
901907
model.IsCertificatesUIEnabled = _contentObjectService.CertificatesConfiguration?.IsUIEnabledForUser(currentUser) ?? false;
902908
model.IsAtomFeedEnabled = _featureFlagService.IsPackagesAtomFeedEnabled();
903-
model.IsPackageDeprecationEnabled = _featureFlagService.IsManageDeprecationEnabled(currentUser, allVersions);
904-
model.IsPackageVulnerabilitiesEnabled = _featureFlagService.IsDisplayVulnerabilitiesEnabled();
909+
model.IsPackageDeprecationEnabled = isPackageDeprecationEnabled;
910+
model.IsPackageVulnerabilitiesEnabled = isPackageVulnerabilitiesEnabled;
905911
model.IsFuGetLinksEnabled = _featureFlagService.IsDisplayFuGetLinksEnabled();
906912
model.IsPackageRenamesEnabled = _featureFlagService.IsPackageRenamesEnabled(currentUser);
907913
model.IsPackageDependentsEnabled = _featureFlagService.IsPackageDependentsEnabled(currentUser);

src/NuGetGallery/Helpers/ViewModelExtensions/DisplayPackageViewModelFactory.cs

Lines changed: 64 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -183,7 +183,8 @@ private DisplayPackageViewModel SetupCommon(
183183
}
184184
}
185185

186-
if (packageKeyToDeprecation != null && packageKeyToDeprecation.TryGetValue(package.Key, out var deprecation))
186+
PackageDeprecation deprecation = null;
187+
if (packageKeyToDeprecation != null && packageKeyToDeprecation.TryGetValue(package.Key, out deprecation))
187188
{
188189
viewModel.DeprecationStatus = deprecation.Status;
189190
}
@@ -192,15 +193,75 @@ private DisplayPackageViewModel SetupCommon(
192193
viewModel.DeprecationStatus = PackageDeprecationStatus.NotDeprecated;
193194
}
194195

195-
if (packageKeyToVulnerabilities != null && packageKeyToVulnerabilities.TryGetValue(package.Key, out var vulnerabilities))
196+
PackageVulnerabilitySeverity? maxVulnerabilitySeverity = null;
197+
if (packageKeyToVulnerabilities != null
198+
&& packageKeyToVulnerabilities.TryGetValue(package.Key, out var vulnerabilities)
199+
&& vulnerabilities != null && vulnerabilities.Any())
196200
{
197201
viewModel.Vulnerabilities = vulnerabilities;
198-
viewModel.MaxVulnerabilitySeverity = vulnerabilities.Max(v => v.Severity);
202+
maxVulnerabilitySeverity = viewModel.Vulnerabilities.Max(v => v.Severity); // cache for messaging
203+
viewModel.MaxVulnerabilitySeverity = maxVulnerabilitySeverity.Value;
204+
}
205+
else
206+
{
207+
viewModel.Vulnerabilities = null;
208+
viewModel.MaxVulnerabilitySeverity = default;
199209
}
200210

211+
viewModel.PackageWarningIconTitle =
212+
GetWarningIconTitle(viewModel.Version, deprecation, maxVulnerabilitySeverity);
213+
201214
return viewModel;
202215
}
203216

217+
private static string GetWarningIconTitle(
218+
string version,
219+
PackageDeprecation deprecation,
220+
PackageVulnerabilitySeverity? maxVulnerabilitySeverity)
221+
{
222+
// We want a tooltip title for the warning icon, which concatenates deprecation and vulnerability information cleanly
223+
var deprecationTitle = "";
224+
if (deprecation != null)
225+
{
226+
deprecationTitle = version;
227+
var isLegacy = deprecation.Status.HasFlag(PackageDeprecationStatus.Legacy);
228+
var hasCriticalBugs = deprecation.Status.HasFlag(PackageDeprecationStatus.CriticalBugs);
229+
if (hasCriticalBugs)
230+
{
231+
if (isLegacy)
232+
{
233+
deprecationTitle += " is deprecated because it's legacy and has critical bugs";
234+
}
235+
else
236+
{
237+
deprecationTitle += " is deprecated because it has critical bugs";
238+
}
239+
}
240+
else if (isLegacy)
241+
{
242+
deprecationTitle += " is deprecated because it's legacy and no longer maintained";
243+
}
244+
else
245+
{
246+
deprecationTitle += " is deprecated";
247+
}
248+
}
249+
250+
if (maxVulnerabilitySeverity.HasValue)
251+
{
252+
var severity = Enum.GetName(typeof(PackageVulnerabilitySeverity), maxVulnerabilitySeverity)?.ToLowerInvariant() ?? "unknown";
253+
var vulnerabilitiesTitle = $"{version} has at least one vulnerability with {severity} severity.";
254+
255+
return string.IsNullOrEmpty(deprecationTitle)
256+
? vulnerabilitiesTitle
257+
: $"{deprecationTitle}; {vulnerabilitiesTitle}";
258+
}
259+
260+
return string.IsNullOrEmpty(deprecationTitle)
261+
? string.Empty
262+
: $"{deprecationTitle}.";
263+
}
264+
204265
private static string GetPushedBy(Package package, User currentUser, Dictionary<User, string> pushedByCache)
205266
{
206267
var userPushedBy = package.User;

src/NuGetGallery/NuGetGallery.csproj

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2305,6 +2305,9 @@
23052305
<ItemGroup>
23062306
<Content Include="App_Data\Files\Content\Query-Hint-Configuration.json" />
23072307
</ItemGroup>
2308+
<ItemGroup>
2309+
<Content Include="Views\Packages\_DisplayPackageVulnerabilities.cshtml" />
2310+
</ItemGroup>
23082311
<PropertyGroup>
23092312
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">10.0</VisualStudioVersion>
23102313
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>

src/NuGetGallery/Scripts/gallery/page-display-package.js

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,23 +4,37 @@ $(function () {
44
// Configure the rename information container
55
window.nuget.configureExpander("rename-content-container", "ChevronDown", null, "ChevronUp");
66
configureExpanderWithEnterKeydown($('#show-rename-content-container'));
7+
var expanderAttributes = ['data-toggle', 'data-target', 'aria-expanded', 'aria-controls', 'tabindex'];
8+
9+
// Configure the vulnerability information container
10+
var vulnerabilitiesContainer = $('#show-vulnerabilities-content-container');
11+
if ($('#vulnerabilities-content-container').children().length) {
12+
// If the vulnerability information container has content, configure it as an expander.
13+
window.nuget.configureExpander("vulnerabilities-content-container", "ChevronDown", null, "ChevronUp");
14+
configureExpanderWithEnterKeydown(vulnerabilitiesContainer);
15+
} else {
16+
// If the container does not have content, remove its expander attributes
17+
expanderAttributes.forEach(attribute => vulnerabilitiesContainer.removeAttr(attribute));
18+
19+
// The expander should not be clickable when it doesn't have content
20+
vulnerabilitiesContainer.find('.vulnerabilities-expander').removeAttr('role');
21+
22+
$('#vulnerabilities-expander-icon-right').hide();
23+
}
724

825
// Configure the deprecation information container
9-
var container = $('#show-deprecation-content-container');
26+
var deprecationContainer = $('#show-deprecation-content-container');
1027
if ($('#deprecation-content-container').children().length) {
1128
// If the deprecation information container has content, configure it as an expander.
1229
window.nuget.configureExpander("deprecation-content-container", "ChevronDown", null, "ChevronUp");
13-
configureExpanderWithEnterKeydown(container)
30+
configureExpanderWithEnterKeydown(deprecationContainer);
1431
}
1532
else {
1633
// If the container does not have content, remove its expander attributes
17-
var expanderAttributes = ['data-toggle', 'data-target', 'aria-expanded', 'aria-controls', 'tabindex'];
18-
for (var i in expanderAttributes) {
19-
container.removeAttr(expanderAttributes[i]);
20-
}
34+
expanderAttributes.forEach(attribute => deprecationContainer.removeAttr(attribute));
2135

2236
// The expander should not be clickable when it doesn't have content
23-
container.find('.deprecation-expander').removeAttr('role');
37+
deprecationContainer.find('.deprecation-expander').removeAttr('role');
2438

2539
$('#deprecation-expander-icon-right').hide();
2640
}

src/NuGetGallery/ViewModels/DisplayPackageViewModel.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ public bool HasNewerRelease
8787
public PackageDeprecationStatus DeprecationStatus { get; set; }
8888
public IReadOnlyCollection<PackageVulnerability> Vulnerabilities { get; set; }
8989
public PackageVulnerabilitySeverity MaxVulnerabilitySeverity { get; set; }
90+
public string PackageWarningIconTitle { get; set; }
9091
public string AlternatePackageId { get; set; }
9192
public string AlternatePackageVersion { get; set; }
9293
public string CustomMessage { get; set; }

src/NuGetGallery/Views/Packages/DisplayPackage.cshtml

Lines changed: 18 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -283,6 +283,11 @@
283283
)
284284
}
285285

286+
@if (Model.IsPackageVulnerabilitiesEnabled && Model.Vulnerabilities != null)
287+
{
288+
@Html.Partial("_DisplayPackageVulnerabilities")
289+
}
290+
286291
@if (Model.IsPackageDeprecationEnabled && Model.DeprecationStatus != PackageDeprecationStatus.NotDeprecated)
287292
{
288293
@Html.Partial("_DisplayPackageDeprecation")
@@ -359,7 +364,7 @@
359364

360365
@AppendAvailableSymbolsMessage(hasSymbolsPackageAvailable)
361366
</text>
362-
)
367+
)
363368
}
364369
else if (Model.LatestSymbolsPackage.StatusKey == PackageStatus.FailedValidation)
365370
{
@@ -404,7 +409,7 @@
404409
@ViewHelpers.AlertWarning(
405410
@<text>
406411
The owner has unlisted this package.
407-
This could mean that the package is deprecated or shouldn't be used anymore.
412+
This could mean that the package is deprecated, has security vulnerabilities or shouldn't be used anymore.
408413
</text>
409414
)
410415
}
@@ -684,9 +689,9 @@
684689
{
685690
<th aria-hidden="true" abbr="Signature Information"></th>
686691
}
687-
@if (Model.IsPackageDeprecationEnabled)
692+
@if (Model.IsPackageDeprecationEnabled || Model.IsPackageVulnerabilitiesEnabled)
688693
{
689-
<th aria-hidden="true" abbr="Deprecation Information"></th>
694+
<th aria-hidden="true" abbr="Package Warnings"></th>
690695
}
691696
</tr>
692697
</thead>
@@ -753,41 +758,16 @@
753758
</td>
754759
}
755760
}
756-
@if (Model.IsPackageDeprecationEnabled)
757-
{
758-
if (packageVersion.DeprecationStatus == PackageDeprecationStatus.NotDeprecated)
759-
{
760-
<td class="package-icon-cell" aria-hidden="true"></td>
761-
}
762-
else
763-
{
764-
var deprecationTitle = packageVersion.Version;
765-
var isLegacy = packageVersion.DeprecationStatus.HasFlag(PackageDeprecationStatus.Legacy);
766-
var hasCriticalBugs = packageVersion.DeprecationStatus.HasFlag(PackageDeprecationStatus.CriticalBugs);
767-
if (hasCriticalBugs)
768-
{
769-
if (isLegacy)
770-
{
771-
deprecationTitle += " is deprecated because it's legacy and has critical bugs.";
772-
}
773-
else
774-
{
775-
deprecationTitle += " is deprecated because it has critical bugs.";
776-
}
777-
}
778-
else if (isLegacy)
779-
{
780-
deprecationTitle += " is deprecated because it's legacy and no longer maintained.";
781-
}
782-
else
783-
{
784-
deprecationTitle += " is deprecated.";
785-
}
786761

787-
<td class="package-icon-cell">
788-
<i class="ms-Icon ms-Icon--Warning package-icon" title="@deprecationTitle"></i>
789-
</td>
790-
}
762+
@if (string.IsNullOrEmpty(packageVersion.PackageWarningIconTitle))
763+
{
764+
<td class="package-icon-cell" aria-hidden="true"></td>
765+
}
766+
else
767+
{
768+
<td class="package-icon-cell">
769+
<i class="ms-Icon ms-Icon--Warning package-icon" title="@packageVersion.PackageWarningIconTitle"></i>
770+
</td>
791771
}
792772
</tr>
793773
}

0 commit comments

Comments
 (0)