Skip to content
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

[Search DV] Display deprecation and vulnerabilities on search page. #9440

Merged
merged 12 commits into from
Apr 26, 2023
Merged
26 changes: 26 additions & 0 deletions src/Bootstrap/dist/css/bootstrap-theme.css

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

27 changes: 27 additions & 0 deletions src/Bootstrap/less/theme/common-list-packages.less
Original file line number Diff line number Diff line change
Expand Up @@ -58,3 +58,30 @@
margin-top: 75px;
margin-bottom: 0px;
}

@severe-warning-background-color: rgb(254, 217, 204);
@severe-warning-icon-color: rgb(216, 59, 1);
@warning-background-color: rgb(255, 244, 206);
@package-warning-color: rgb(50, 49, 48);
@badge-border-radius: 2px;
dannyjdev marked this conversation as resolved.
Show resolved Hide resolved

.package-warning {
padding-right: 8px;
padding-left: 8px;
border-radius: @badge-border-radius;
margin-right: 5px;
color: @package-warning-color
}

.package-warning--vulnerable {
.package-warning;
background-color: @severe-warning-background-color;
i {
color: @severe-warning-icon-color;
}
}

.package-warning--deprecated {
.package-warning;
background-color: @warning-background-color;
}
84 changes: 84 additions & 0 deletions src/NuGetGallery/Helpers/SearchResponseHelper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
// 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.

using System;
using System.Collections.Generic;
using System.Linq;
using Newtonsoft.Json.Linq;
using NuGet.Services.Entities;

namespace NuGetGallery.Helpers
{
public static class SearchResponseHelper
{
public static ICollection<PackageDeprecation> GetDeprecationsOrNull(JToken docDeprecation)
agr marked this conversation as resolved.
Show resolved Hide resolved
{
PackageDeprecation deprecation = null;
if (docDeprecation != null)
{
var docReasons = docDeprecation.Value<JArray>("Reasons");
if (docReasons != null && docReasons.HasValues)
{
PackageDeprecationStatus status = PackageDeprecationStatus.NotDeprecated;
foreach (var reason in docReasons)
{
if (Enum.TryParse<PackageDeprecationStatus>(reason.Value<string>(), out var pdStatus))
{
status |= pdStatus;
}
}

var docAlternatePackage = docDeprecation["AlternatePackage"];
Package alternatePackage = null;
if (docAlternatePackage != null)
{
var range = docAlternatePackage.Value<string>("Range");
var id = docAlternatePackage.Value<string>("Id");
if (!string.IsNullOrEmpty(range) && !string.IsNullOrEmpty(id))
{
var version = string.Empty;
if (range.StartsWith("["))
{
version = range.Substring(1, range.IndexOf(", )"));
dannyjdev marked this conversation as resolved.
Show resolved Hide resolved
}

alternatePackage = new Package()
{
Id = id,
Version = version
};
}
}

deprecation = new PackageDeprecation()
{
CustomMessage = docDeprecation.Value<string>("Message"),
Status = status,
AlternatePackage = alternatePackage
};
}
}

return deprecation == null ? null : new List<PackageDeprecation>() { deprecation };
joelverhagen marked this conversation as resolved.
Show resolved Hide resolved
}

public static ICollection<VulnerablePackageVersionRange> GetVulnerabilities(JArray docVulnerabilities)
{
var vulnerabilities = new List<VulnerablePackageVersionRange>();
if (docVulnerabilities != null)
{
vulnerabilities = docVulnerabilities.Select(v => new VulnerablePackageVersionRange()
{
Vulnerability = new PackageVulnerability()
{
AdvisoryUrl = v.Value<string>("AdvisoryUrl"),
Severity = (PackageVulnerabilitySeverity)v.Value<int>("Severity")
}
})
.ToList();
}

return vulnerabilities;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using System.Linq;
using NuGet.Services.Entities;
using NuGet.Versioning;
using NuGetGallery.Helpers;

namespace NuGetGallery
{
Expand Down Expand Up @@ -217,60 +218,11 @@ private DisplayPackageViewModel SetupCommon(
viewModel.MaxVulnerabilitySeverity = default;
}

viewModel.PackageWarningIconTitle =
GetWarningIconTitle(viewModel.Version, deprecation, maxVulnerabilitySeverity);
viewModel.PackageWarningIconTitle = WarningTitleHelper.GetWarningIconTitle(viewModel.Version, deprecation, maxVulnerabilitySeverity);

return viewModel;
}

private static string GetWarningIconTitle(
string version,
PackageDeprecation deprecation,
PackageVulnerabilitySeverity? maxVulnerabilitySeverity)
{
// We want a tooltip title for the warning icon, which concatenates deprecation and vulnerability information cleanly
var deprecationTitle = "";
if (deprecation != null)
{
deprecationTitle = version;
var isLegacy = deprecation.Status.HasFlag(PackageDeprecationStatus.Legacy);
var hasCriticalBugs = deprecation.Status.HasFlag(PackageDeprecationStatus.CriticalBugs);
if (hasCriticalBugs)
{
if (isLegacy)
{
deprecationTitle += " is deprecated because it's legacy and has critical bugs";
}
else
{
deprecationTitle += " is deprecated because it has critical bugs";
}
}
else if (isLegacy)
{
deprecationTitle += " is deprecated because it's legacy and no longer maintained";
}
else
{
deprecationTitle += " is deprecated";
}
}

if (maxVulnerabilitySeverity.HasValue)
{
var severity = Enum.GetName(typeof(PackageVulnerabilitySeverity), maxVulnerabilitySeverity)?.ToLowerInvariant() ?? "unknown";
var vulnerabilitiesTitle = $"{version} has at least one vulnerability with {severity} severity.";

return string.IsNullOrEmpty(deprecationTitle)
? vulnerabilitiesTitle
: $"{deprecationTitle}; {vulnerabilitiesTitle}";
}

return string.IsNullOrEmpty(deprecationTitle)
? string.Empty
: $"{deprecationTitle}.";
}

private static string GetPushedBy(Package package, User currentUser, Dictionary<User, string> pushedByCache)
{
var userPushedBy = package.User;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

using System.Linq;
using NuGet.Services.Entities;
using NuGetGallery.Helpers;

namespace NuGetGallery
{
Expand Down Expand Up @@ -39,6 +40,19 @@ private ListPackageItemViewModel SetupInternal(ListPackageItemViewModel viewMode
viewModel.MinClientVersion = package.MinClientVersion;
viewModel.Owners = package.PackageRegistration?.Owners?.Select(GetBasicUserViewModel).ToList();
viewModel.IsVerified = package.PackageRegistration?.IsVerified;
viewModel.IsDeprecated = package.Deprecations?.Count > 0;
viewModel.IsVulnerable = package.VulnerablePackageRanges?.Count > 0;

if (viewModel.IsDeprecated)
{
viewModel.DeprecationTitle = WarningTitleHelper.GetDeprecationTitle(package.Version, package.Deprecations.First().Status);
}

if (viewModel.IsVulnerable)
{
var maxVulnerabilitySeverity = package.VulnerablePackageRanges.Max(vpr => vpr.Vulnerability.Severity);
viewModel.VulnerabilityTitle = WarningTitleHelper.GetVulnerabilityTitle(package.Version, maxVulnerabilitySeverity);
}

viewModel.CanDisplayPrivateMetadata = CanPerformAction(currentUser, package, ActionsRequiringPermissions.DisplayPrivatePackageMetadata);
viewModel.CanEdit = CanPerformAction(currentUser, package, ActionsRequiringPermissions.EditPackage);
Expand Down Expand Up @@ -68,9 +82,10 @@ private static bool CanPerformAction(User currentUser, Package package, ActionRe

private static BasicUserViewModel GetBasicUserViewModel(User user)
{
return new BasicUserViewModel {
Username = user.Username,
EmailAddress = user.EmailAddress,
return new BasicUserViewModel
{
Username = user.Username,
EmailAddress = user.EmailAddress,
IsOrganization = user is Organization,
IsLocked = user.IsLocked
};
Expand Down
69 changes: 69 additions & 0 deletions src/NuGetGallery/Helpers/WarningTitleHelper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
// 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.

using System;
using NuGet.Services.Entities;

namespace NuGetGallery.Helpers
{
public static class WarningTitleHelper
{
public static string GetWarningIconTitle(
string version,
PackageDeprecation deprecation,
PackageVulnerabilitySeverity? maxVulnerabilitySeverity)
{
// We want a tooltip title for the warning icon, which concatenates deprecation and vulnerability information cleanly
var deprecationTitle = "";
if (deprecation != null)
{
deprecationTitle = GetDeprecationTitle(version, deprecation.Status);
}

if (maxVulnerabilitySeverity.HasValue)
{
var vulnerabilitiesTitle = GetVulnerabilityTitle(version, maxVulnerabilitySeverity.Value);
return string.IsNullOrEmpty(deprecationTitle)
? vulnerabilitiesTitle
: $"{deprecationTitle.TrimEnd('.')}; {vulnerabilitiesTitle}";
}

return string.IsNullOrEmpty(deprecationTitle) ? string.Empty : deprecationTitle;
}

public static string GetVulnerabilityTitle(string version, PackageVulnerabilitySeverity maxVulnerabilitySeverity)
{
var severity = Enum.GetName(typeof(PackageVulnerabilitySeverity), maxVulnerabilitySeverity)?.ToLowerInvariant() ?? "unknown";
return $"{version} has at least one vulnerability with {severity} severity.";
}

public static string GetDeprecationTitle(string version, PackageDeprecationStatus status)
{
var deprecationTitle = version;
var isLegacy = status.HasFlag(PackageDeprecationStatus.Legacy);
var hasCriticalBugs = status.HasFlag(PackageDeprecationStatus.CriticalBugs);

if (hasCriticalBugs)
{
if (isLegacy)
{
deprecationTitle += " is deprecated because it is no longer maintained and has critical bugs";
}
else
{
deprecationTitle += " is deprecated because it has critical bugs";
}
}
else if (isLegacy)
{
deprecationTitle += " is deprecated because it is no longer maintained";
}
else
{
deprecationTitle += " is deprecated";
dannyjdev marked this conversation as resolved.
Show resolved Hide resolved
}

return $"{deprecationTitle}.";
}
}
}
Loading