Skip to content

Commit 5871c77

Browse files
authored
Add IsVulnerable to Manage Packages view model (#8435)
Expand view models for vulnerabilities in manage packages page
1 parent 5882f3e commit 5871c77

14 files changed

Lines changed: 169 additions & 59 deletions

File tree

src/NuGetGallery.Services/PackageManagement/IPackageService.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -87,9 +87,9 @@ Package FilterLatestPackage(
8787
Package FilterLatestPackageBySuffix(IReadOnlyCollection<Package> packages,
8888
string version, bool prerelease);
8989

90-
IEnumerable<Package> FindPackagesByOwner(User user, bool includeUnlisted, bool includeVersions = false);
90+
IEnumerable<Package> FindPackagesByOwner(User user, bool includeUnlisted, bool includeVersions = false, bool includeVulnerabilities = false);
9191

92-
IEnumerable<Package> FindPackagesByAnyMatchingOwner(User user, bool includeUnlisted, bool includeVersions = false);
92+
IEnumerable<Package> FindPackagesByAnyMatchingOwner(User user, bool includeUnlisted, bool includeVersions = false, bool includeVulnerabilities = false);
9393

9494
IQueryable<PackageRegistration> FindPackageRegistrationsByOwner(User user);
9595

src/NuGetGallery.Services/PackageManagement/PackageService.cs

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -406,9 +406,12 @@ private static Package FilterLatestPackageHelper(
406406
return package;
407407
}
408408

409-
public IEnumerable<Package> FindPackagesByOwner(User user, bool includeUnlisted, bool includeVersions = false)
409+
public IEnumerable<Package> FindPackagesByOwner(User user,
410+
bool includeUnlisted,
411+
bool includeVersions = false,
412+
bool includeVulnerabilities = false)
410413
{
411-
return GetPackagesForOwners(new[] { user.Key }, includeUnlisted, includeVersions);
414+
return GetPackagesForOwners(new[] { user.Key }, includeUnlisted, includeVersions, includeVulnerabilities);
412415
}
413416

414417
/// <summary>
@@ -417,15 +420,17 @@ public IEnumerable<Package> FindPackagesByOwner(User user, bool includeUnlisted,
417420
public IEnumerable<Package> FindPackagesByAnyMatchingOwner(
418421
User user,
419422
bool includeUnlisted,
420-
bool includeVersions = false)
423+
bool includeVersions = false,
424+
bool includeVulnerabilities = false)
421425
{
422426
var ownerKeys = user.Organizations.Select(org => org.OrganizationKey).ToList();
423427
ownerKeys.Insert(0, user.Key);
424428

425-
return GetPackagesForOwners(ownerKeys, includeUnlisted, includeVersions);
429+
return GetPackagesForOwners(ownerKeys, includeUnlisted, includeVersions, includeVulnerabilities);
426430
}
427431

428-
private IEnumerable<Package> GetPackagesForOwners(IEnumerable<int> ownerKeys, bool includeUnlisted, bool includeVersions)
432+
private IEnumerable<Package> GetPackagesForOwners(IEnumerable<int> ownerKeys, bool includeUnlisted,
433+
bool includeVersions, bool includeVulnerabilities)
429434
{
430435
IQueryable<Package> packages = _packageRepository.GetAll()
431436
.Where(p => p.PackageRegistration.Owners.Any(o => ownerKeys.Contains(o.Key)));
@@ -437,7 +442,7 @@ private IEnumerable<Package> GetPackagesForOwners(IEnumerable<int> ownerKeys, bo
437442

438443
if (!includeVersions)
439444
{
440-
// Do a best effort of retrieving the latest version. Note that UpdateIsLatest has had concurrency issues
445+
// Do a best effort of retrieving the latest versions. Note that UpdateIsLatest has had concurrency issues
441446
// where sometimes packages no rows with IsLatest set. In this case, we'll just select the last inserted
442447
// row (descending [Key]) as opposed to reading all rows into memory and sorting on NuGetVersion.
443448
packages = packages
@@ -453,11 +458,17 @@ private IEnumerable<Package> GetPackagesForOwners(IEnumerable<int> ownerKeys, bo
453458
.FirstOrDefault());
454459
}
455460

456-
return packages
461+
var result = packages
457462
.Include(p => p.PackageRegistration)
458463
.Include(p => p.PackageRegistration.Owners)
459-
.Include(p => p.PackageRegistration.RequiredSigners)
460-
.ToList();
464+
.Include(p => p.PackageRegistration.RequiredSigners);
465+
466+
if (includeVulnerabilities)
467+
{
468+
result = result.Include($"{nameof(Package.VulnerablePackageRanges)}.{nameof(VulnerablePackageVersionRange.Vulnerability)}");
469+
}
470+
471+
return result.ToList();
461472
}
462473

463474
public IQueryable<PackageRegistration> FindPackageRegistrationsByOwner(User user)

src/NuGetGallery/Controllers/UsersController.cs

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ public partial class UsersController
3535
private readonly ListPackageItemViewModelFactory _listPackageItemViewModelFactory;
3636
private readonly ISupportRequestService _supportRequestService;
3737
private readonly IFeatureFlagService _featureFlagService;
38+
private readonly IPackageVulnerabilitiesService _packageVulnerabilitiesService;
3839

3940
public UsersController(
4041
IUserService userService,
@@ -51,6 +52,7 @@ public UsersController(
5152
ICertificateService certificateService,
5253
IContentObjectService contentObjectService,
5354
IFeatureFlagService featureFlagService,
55+
IPackageVulnerabilitiesService packageVulnerabilitiesService,
5456
IMessageServiceConfiguration messageServiceConfiguration,
5557
IIconUrlProvider iconUrlProvider,
5658
IGravatarProxyService gravatarProxy)
@@ -73,8 +75,10 @@ public UsersController(
7375
_credentialBuilder = credentialBuilder ?? throw new ArgumentNullException(nameof(credentialBuilder));
7476
_supportRequestService = supportRequestService ?? throw new ArgumentNullException(nameof(supportRequestService));
7577
_featureFlagService = featureFlagService ?? throw new ArgumentNullException(nameof(featureFlagService));
78+
_packageVulnerabilitiesService = packageVulnerabilitiesService ?? throw new ArgumentNullException(nameof(packageVulnerabilitiesService));
7679

77-
_listPackageItemRequiredSignerViewModelFactory = new ListPackageItemRequiredSignerViewModelFactory(securityPolicyService, iconUrlProvider);
80+
_listPackageItemRequiredSignerViewModelFactory = new ListPackageItemRequiredSignerViewModelFactory(
81+
securityPolicyService, iconUrlProvider, packageVulnerabilitiesService);
7882
_listPackageItemViewModelFactory = new ListPackageItemViewModelFactory(iconUrlProvider);
7983
}
8084

@@ -521,7 +525,8 @@ public virtual ActionResult Packages()
521525

522526
var wasAADLoginOrMultiFactorAuthenticated = User.WasMultiFactorAuthenticated() || User.WasAzureActiveDirectoryAccountUsedForSignin();
523527

524-
var packages = PackageService.FindPackagesByAnyMatchingOwner(currentUser, includeUnlisted: true);
528+
var packages = PackageService.FindPackagesByAnyMatchingOwner(
529+
currentUser, includeUnlisted: true, includeVulnerabilities: true);
525530

526531
var listedPackages = GetPackages(packages, currentUser, wasAADLoginOrMultiFactorAuthenticated,
527532
p => p.Listed && p.PackageStatusKey == PackageStatus.Available);
@@ -559,8 +564,10 @@ public virtual ActionResult Packages()
559564
OwnerRequests = ownerRequests,
560565
ReservedNamespaces = reservedPrefixes,
561566
WasMultiFactorAuthenticated = User.WasMultiFactorAuthenticated(),
562-
IsCertificatesUIEnabled = ContentObjectService.CertificatesConfiguration?.IsUIEnabledForUser(currentUser) ?? false
567+
IsCertificatesUIEnabled = ContentObjectService.CertificatesConfiguration?.IsUIEnabledForUser(currentUser) ?? false,
568+
IsPackageVulnerabilitiesEnabled = _featureFlagService.IsDisplayVulnerabilitiesEnabled()
563569
};
570+
564571
return View(model);
565572
}
566573

src/NuGetGallery/Helpers/ViewModelExtensions/ListPackageItemRequiredSignerViewModelFactory.cs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,16 @@ public class ListPackageItemRequiredSignerViewModelFactory
1313
{
1414
private readonly ListPackageItemViewModelFactory _listPackageItemViewModelFactory;
1515
private readonly ISecurityPolicyService _securityPolicyService;
16+
private readonly IPackageVulnerabilitiesService _packageVulnerabilitiesService;
1617

17-
public ListPackageItemRequiredSignerViewModelFactory(ISecurityPolicyService securityPolicyService, IIconUrlProvider iconUrlProvider)
18+
public ListPackageItemRequiredSignerViewModelFactory(
19+
ISecurityPolicyService securityPolicyService,
20+
IIconUrlProvider iconUrlProvider,
21+
IPackageVulnerabilitiesService packageVulnerabilitiesService)
1822
{
1923
_listPackageItemViewModelFactory = new ListPackageItemViewModelFactory(iconUrlProvider);
2024
_securityPolicyService = securityPolicyService ?? throw new ArgumentNullException(nameof(securityPolicyService));
25+
_packageVulnerabilitiesService = packageVulnerabilitiesService ?? throw new ArgumentNullException(nameof(packageVulnerabilitiesService));
2126
}
2227

2328
// username must be an empty string because <select /> option values are based on username
@@ -125,6 +130,8 @@ private ListPackageItemRequiredSignerViewModel SetupInternal(
125130
viewModel.CanEditRequiredSigner &= wasAADLoginOrMultiFactorAuthenticated;
126131
}
127132

133+
viewModel.IsVulnerable = _packageVulnerabilitiesService.IsPackageVulnerable(package);
134+
128135
return viewModel;
129136
}
130137

src/NuGetGallery/Services/IPackageVulnerabilitiesService.cs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
33

44
using System.Collections.Generic;
5-
using System.Threading.Tasks;
65
using NuGet.Services.Entities;
76

87
namespace NuGetGallery
@@ -14,5 +13,11 @@ public interface IPackageVulnerabilitiesService
1413
/// </summary>
1514
/// <param name="id">id of the package for this query</param>
1615
IReadOnlyDictionary<int, IReadOnlyList<PackageVulnerability>> GetVulnerabilitiesById(string id);
16+
17+
/// <summary>
18+
/// Returns true if the package has a vulnerability
19+
/// </summary>
20+
/// <param name="package">package to examine</param>
21+
bool IsPackageVulnerable(Package package);
1722
}
1823
}

src/NuGetGallery/Services/PackageVulnerabilitiesService.cs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
using System;
55
using System.Collections.Generic;
66
using System.Data.Entity;
7+
using System.IO;
78
using System.Linq;
89
using NuGet.Services.Entities;
910

@@ -41,5 +42,20 @@ public IReadOnlyDictionary<int, IReadOnlyList<PackageVulnerability>> GetVulnerab
4142
return !result.Any() ? null :
4243
result.ToDictionary(kv => kv.Key, kv => kv.Value as IReadOnlyList<PackageVulnerability>);
4344
}
45+
46+
public bool IsPackageVulnerable(Package package)
47+
{
48+
if (package == null)
49+
{
50+
throw new ArgumentNullException(nameof(package));
51+
}
52+
53+
if (package.VulnerablePackageRanges == null)
54+
{
55+
throw new ArgumentException($"{nameof(package.VulnerablePackageRanges)} should be included in package query");
56+
}
57+
58+
return package.VulnerablePackageRanges.FirstOrDefault(vpr => vpr.Vulnerability != null) != null;
59+
}
4460
}
4561
}

src/NuGetGallery/ViewModels/ManagePackagesViewModel.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,5 +23,7 @@ public class ManagePackagesViewModel
2323
public bool WasMultiFactorAuthenticated { get; set; }
2424

2525
public bool IsCertificatesUIEnabled { get; set; }
26+
27+
public bool IsPackageVulnerabilitiesEnabled { get; set; }
2628
}
2729
}

src/NuGetGallery/ViewModels/PackageViewModel.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ public class PackageViewModel : IPackageVersionModel
2828
public string Version { get; set; }
2929
public string FullVersion { get; set; }
3030
public PackageStatusSummary PackageStatusSummary { get; set; }
31+
public bool IsVulnerable { get; set; }
3132

3233
public bool IsCurrent(IPackageVersionModel current)
3334
{

tests/AccountDeleter.Facts/EvaluatorFacts.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ public EvaluatorFacts(ITestOutputHelper output)
3131
public void NuGetDeleteevaluatorReturnsCorrectValueForUnconfirmed()
3232
{
3333
// Setup
34-
_packageService.Setup(ps => ps.FindPackagesByOwner(It.IsAny<User>(), It.IsAny<bool>(), It.IsAny<bool>()))
34+
_packageService.Setup(ps => ps.FindPackagesByOwner(It.IsAny<User>(), It.IsAny<bool>(), It.IsAny<bool>(), It.IsAny<bool>()))
3535
.Returns(() =>
3636
{
3737
return new List<Package>();
@@ -58,7 +58,7 @@ public void NuGetDeleteevaluatorReturnsCorrectValueForUnconfirmed()
5858
public void NuGetDeleteevaluatorReturnsCorrectValueForPackages(bool userHasPackages, bool expectedResult)
5959
{
6060
// Setup
61-
_packageService.Setup(ps => ps.FindPackagesByOwner(It.IsAny<User>(), It.IsAny<bool>(), It.IsAny<bool>()))
61+
_packageService.Setup(ps => ps.FindPackagesByOwner(It.IsAny<User>(), It.IsAny<bool>(), It.IsAny<bool>(), It.IsAny<bool>()))
6262
.Returns(() =>
6363
{
6464
if (userHasPackages)
@@ -94,7 +94,7 @@ public void NuGetDeleteevaluatorReturnsCorrectValueForPackages(bool userHasPacka
9494
public void NuGetDeleteevaluatorReturnsCorrectValueForOrganizations(bool userHasOrgs, bool userIsAdmin, bool expectedResult)
9595
{
9696
// Setup
97-
_packageService.Setup(ps => ps.FindPackagesByOwner(It.IsAny<User>(), It.IsAny<bool>(), It.IsAny<bool>()))
97+
_packageService.Setup(ps => ps.FindPackagesByOwner(It.IsAny<User>(), It.IsAny<bool>(), It.IsAny<bool>(), It.IsAny<bool>()))
9898
.Returns(() =>
9999
{
100100
return new List<Package>();

tests/NuGetGallery.Facts/Controllers/OrganizationsControllerFacts.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1665,7 +1665,7 @@ public async Task IfAdministrator_ShowsViewWithCorrectData(bool isPackageOrphane
16651665
.Setup(stub => stub.FindByUsername(testOrganization.Username, false))
16661666
.Returns(testOrganization);
16671667
GetMock<IPackageService>()
1668-
.Setup(stub => stub.FindPackagesByAnyMatchingOwner(testOrganization, It.IsAny<bool>(), false))
1668+
.Setup(stub => stub.FindPackagesByAnyMatchingOwner(testOrganization, It.IsAny<bool>(), false, false))
16691669
.Returns(userPackages);
16701670
GetMock<IPackageService>()
16711671
.Setup(stub => stub.WillPackageBeOrphanedIfOwnerRemoved(packageRegistration, testOrganization))
@@ -1700,7 +1700,7 @@ public async Task IfOrphanedPackages_RedirectsToDeleteRequest()
17001700
controller.SetCurrentUser(fakes.OrganizationOwnerAdmin);
17011701

17021702
GetMock<IPackageService>()
1703-
.Setup(x => x.FindPackagesByAnyMatchingOwner(testOrganization, true, false))
1703+
.Setup(x => x.FindPackagesByAnyMatchingOwner(testOrganization, true, false, false))
17041704
.Returns(new[] { new Package { Version = "1.0.0", PackageRegistration = new PackageRegistration { Owners = new[] { testOrganization } } } });
17051705
GetMock<IPackageService>()
17061706
.Setup(x => x.WillPackageBeOrphanedIfOwnerRemoved(It.IsAny<PackageRegistration>(), testOrganization))
@@ -2484,7 +2484,7 @@ public void DeleteHappyAccount(bool withPendingIssues)
24842484
.Setup(stub => stub.FindByUsername(username, false))
24852485
.Returns(testUser);
24862486
GetMock<IPackageService>()
2487-
.Setup(stub => stub.FindPackagesByAnyMatchingOwner(testUser, It.IsAny<bool>(), false))
2487+
.Setup(stub => stub.FindPackagesByAnyMatchingOwner(testUser, It.IsAny<bool>(), false, false))
24882488
.Returns(userPackages);
24892489
const string iconUrl = "https://icon.test/url";
24902490
GetMock<IIconUrlProvider>()

0 commit comments

Comments
 (0)