Skip to content

Commit 03ea2cc

Browse files
authored
Make pva cache case insensitive to support all package details urls (#8793)
1 parent 6534267 commit 03ea2cc

2 files changed

Lines changed: 145 additions & 20 deletions

File tree

src/NuGetGallery/Services/PackageVulnerabilitiesCacheService.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,9 @@ public void RefreshCache(IServiceScopeFactory serviceScopeFactory)
8686
ikv.GroupBy(kv => kv.PackageKey, kv => kv.Vulnerability)
8787
// - build the inner dictionaries, all under the same <id>, each keyed by <package key>
8888
.ToDictionary(kv => kv.Key,
89-
kv => kv.ToList().AsReadOnly() as IReadOnlyList<PackageVulnerability>));
89+
kv => kv.ToList().AsReadOnly() as IReadOnlyList<PackageVulnerability>),
90+
// we need this lookup to be case insensitive for package details page load URLs to work regardless of case
91+
StringComparer.InvariantCultureIgnoreCase);
9092
}
9193

9294
stopwatch.Stop();

tests/NuGetGallery.Facts/Services/PackageVulnerabilitiesCacheServiceFacts.cs

Lines changed: 142 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -15,33 +15,25 @@ namespace NuGetGallery.Services
1515
{
1616
public class PackageVulnerabilitiesCacheServiceFacts
1717
{
18+
private Mock<IServiceScope> _serviceScopeMock;
19+
private Mock<ITelemetryService> _telemetryServiceMock;
20+
private PackageVulnerabilitiesCacheService _cacheService;
21+
1822
[Fact]
1923
public void RefreshesVulnerabilitiesCache()
2024
{
2125
// Arrange
22-
var entitiesContext = new Mock<IEntitiesContext>();
23-
entitiesContext.Setup(x => x.Set<VulnerablePackageVersionRange>()).Returns(GetVulnerableRanges());
24-
var serviceProvider = new Mock<IServiceProvider>();
25-
serviceProvider.Setup(x => x.GetService(typeof(IEntitiesContext))).Returns(entitiesContext.Object);
26-
var serviceScope = new Mock<IServiceScope>();
27-
serviceScope.Setup(x => x.ServiceProvider).Returns(serviceProvider.Object);
28-
serviceScope.Setup(x => x.Dispose()).Verifiable();
29-
var serviceScopeFactory = new Mock<IServiceScopeFactory>();
30-
serviceScopeFactory.Setup(x => x.CreateScope()).Returns(serviceScope.Object);
31-
var telemetryService = new Mock<ITelemetryService>();
32-
telemetryService.Setup(x => x.TrackVulnerabilitiesCacheRefreshDuration(It.IsAny<TimeSpan>())).Verifiable();
33-
var cacheService = new PackageVulnerabilitiesCacheService(telemetryService.Object);
34-
cacheService.RefreshCache(serviceScopeFactory.Object);
26+
Setup();
3527

3628
// Act
37-
var vulnerabilitiesFoo = cacheService.GetVulnerabilitiesById("Foo");
38-
var vulnerabilitiesBar = cacheService.GetVulnerabilitiesById("Bar");
29+
var vulnerabilitiesFoo = _cacheService.GetVulnerabilitiesById("Foo");
30+
var vulnerabilitiesBar = _cacheService.GetVulnerabilitiesById("Bar");
3931

4032
// Assert
4133
// - ensure telemetry is sent
42-
telemetryService.Verify(x => x.TrackVulnerabilitiesCacheRefreshDuration(It.IsAny<TimeSpan>()), Times.Once);
34+
_telemetryServiceMock.Verify(x => x.TrackVulnerabilitiesCacheRefreshDuration(It.IsAny<TimeSpan>()), Times.Once);
4335
// - ensure scope is disposed
44-
serviceScope.Verify(x => x.Dispose(), Times.AtLeastOnce);
36+
_serviceScopeMock.Verify(x => x.Dispose(), Times.AtLeastOnce);
4537
// - ensure contants of cache are correct
4638
Assert.Equal(4, vulnerabilitiesFoo.Count);
4739
Assert.Equal(1, vulnerabilitiesFoo[0].Count);
@@ -56,10 +48,57 @@ public void RefreshesVulnerabilitiesCache()
5648
Assert.Equal(1, vulnerabilitiesBar[6].Count);
5749
}
5850

59-
DbSet<VulnerablePackageVersionRange> GetVulnerableRanges()
51+
[Theory]
52+
[InlineData("Foo", 4)]
53+
[InlineData("Bar", 2)]
54+
[InlineData("FOo", 4)]
55+
[InlineData("FoO", 4)]
56+
[InlineData("Bär", 2)]
57+
[InlineData("BÄr", 2)]
58+
[InlineData("ÇCombinedWithCedilla", 1)] // first char here is C combined with cedilla diacritic: /u00C7 ** actual match
59+
[InlineData("çCombinedWithCedilla", 1)] // first char here is c combined with cedilla diacritic: /u00E7
60+
[InlineData("çCombinedWithCedilla", 1)] // first char here c followed by combining cedilla diacritic: /u0063 /u0327
61+
[InlineData("cCombinedWithCedilla", 0)] // first char here c only (not a match with a c-cedilla): /u0063
62+
[InlineData("ÇFollowedByCedilla", 1)] // first char here is C combined with cedilla diacritic: /u00C7
63+
[InlineData("çFollowedByCedilla", 1)] // first char here is c combined with cedilla diacritic: /u00E7
64+
[InlineData("çFollowedByCedilla", 1)] // first char here c followed by combining cedilla diacritic: /u0063 /u0327 ** actual match
65+
[InlineData("cFollowedByCedilla", 0)] // first char here c only (not a match with a c-cedilla): /u0063
66+
public void CacheSupportsCaseInsensitiveLookups(string packageLookupId, int expectedVulnerabilitiesCount)
67+
{
68+
// Arrange
69+
Setup();
70+
71+
// Act
72+
var vulnerabilitiesFoo = _cacheService.GetVulnerabilitiesById(packageLookupId);
73+
74+
// Assert
75+
Assert.Equal(expectedVulnerabilitiesCount, vulnerabilitiesFoo?.Count ?? 0);
76+
}
77+
78+
private void Setup()
79+
{
80+
var entitiesContext = new Mock<IEntitiesContext>();
81+
entitiesContext.Setup(x => x.Set<VulnerablePackageVersionRange>()).Returns(GetVulnerableRanges());
82+
var serviceProvider = new Mock<IServiceProvider>();
83+
serviceProvider.Setup(x => x.GetService(typeof(IEntitiesContext))).Returns(entitiesContext.Object);
84+
_serviceScopeMock = new Mock<IServiceScope>();
85+
_serviceScopeMock.Setup(x => x.ServiceProvider).Returns(serviceProvider.Object);
86+
_serviceScopeMock.Setup(x => x.Dispose()).Verifiable();
87+
var serviceScopeFactory = new Mock<IServiceScopeFactory>();
88+
serviceScopeFactory.Setup(x => x.CreateScope()).Returns(_serviceScopeMock.Object);
89+
_telemetryServiceMock = new Mock<ITelemetryService>();
90+
_telemetryServiceMock.Setup(x => x.TrackVulnerabilitiesCacheRefreshDuration(It.IsAny<TimeSpan>())).Verifiable();
91+
_cacheService = new PackageVulnerabilitiesCacheService(_telemetryServiceMock.Object);
92+
_cacheService.RefreshCache(serviceScopeFactory.Object);
93+
}
94+
95+
private static DbSet<VulnerablePackageVersionRange> GetVulnerableRanges()
6096
{
6197
var registrationFoo = new PackageRegistration { Id = "Foo" };
6298
var registrationBar = new PackageRegistration { Id = "Bar" };
99+
var registrationBarNonLatin = new PackageRegistration { Id = "Bär" };
100+
var registrationCedillaNonLatin = new PackageRegistration { Id = "ÇCombinedWithCedilla" }; // Ç is /u00C7
101+
var registrationCedillaNonLatin2 = new PackageRegistration { Id = "çFollowedByCedilla" }; // ç is /u0063 /u0327
63102

64103
var vulnerabilityCriticalFoo = new PackageVulnerability
65104
{
@@ -79,6 +118,24 @@ DbSet<VulnerablePackageVersionRange> GetVulnerableRanges()
79118
GitHubDatabaseKey = 9012,
80119
Severity = PackageVulnerabilitySeverity.Critical
81120
};
121+
var vulnerabilityCriticalBarNonLatin = new PackageVulnerability
122+
{
123+
AdvisoryUrl = "http://theurl/2109",
124+
GitHubDatabaseKey = 2109,
125+
Severity = PackageVulnerabilitySeverity.Critical
126+
};
127+
var vulnerabilityCriticalCedillaNonLatin = new PackageVulnerability
128+
{
129+
AdvisoryUrl = "http://theurl/2901",
130+
GitHubDatabaseKey = 2901,
131+
Severity = PackageVulnerabilitySeverity.Critical
132+
};
133+
var vulnerabilityCriticalCedillaNonLatin2 = new PackageVulnerability
134+
{
135+
AdvisoryUrl = "http://theurl/29012",
136+
GitHubDatabaseKey = 29012,
137+
Severity = PackageVulnerabilitySeverity.Critical
138+
};
82139

83140
var versionRangeCriticalFoo = new VulnerablePackageVersionRange
84141
{
@@ -101,6 +158,27 @@ DbSet<VulnerablePackageVersionRange> GetVulnerableRanges()
101158
PackageVersionRange = "<=1.1.0",
102159
FirstPatchedPackageVersion = "1.1.1"
103160
};
161+
var versionRangeCriticalBarNonLatin = new VulnerablePackageVersionRange
162+
{
163+
Vulnerability = vulnerabilityCriticalBarNonLatin,
164+
PackageId = "Bär",
165+
PackageVersionRange = "<=1.1.0",
166+
FirstPatchedPackageVersion = "1.1.1"
167+
};
168+
var versionRangeCriticalCedillaNonLatin = new VulnerablePackageVersionRange
169+
{
170+
Vulnerability = vulnerabilityCriticalCedillaNonLatin,
171+
PackageId = "ÇCombinedWithCedilla",
172+
PackageVersionRange = "<=1.0.0",
173+
FirstPatchedPackageVersion = "1.1.1"
174+
};
175+
var versionRangeCriticalCedillaNonLatin2 = new VulnerablePackageVersionRange
176+
{
177+
Vulnerability = vulnerabilityCriticalCedillaNonLatin2,
178+
PackageId = "çFollowedByCedilla",
179+
PackageVersionRange = "<=1.0.0",
180+
FirstPatchedPackageVersion = "1.1.1"
181+
};
104182

105183
var packageFoo100 = new Package
106184
{
@@ -163,12 +241,57 @@ DbSet<VulnerablePackageVersionRange> GetVulnerableRanges()
163241
versionRangeCriticalBar
164242
}
165243
};
244+
var packageBarNonLatin100 = new Package
245+
{
246+
Key = 5,
247+
Version = "1.0.0",
248+
PackageRegistration = registrationBarNonLatin,
249+
VulnerablePackageRanges = new List<VulnerablePackageVersionRange>
250+
{
251+
versionRangeCriticalBarNonLatin
252+
}
253+
};
254+
var packageBarNonLatin110 = new Package
255+
{
256+
Key = 6,
257+
PackageRegistration = registrationBarNonLatin,
258+
Version = "1.1.0",
259+
VulnerablePackageRanges = new List<VulnerablePackageVersionRange>
260+
{
261+
versionRangeCriticalBarNonLatin
262+
}
263+
};
264+
var packageCedillaNonLatin100 = new Package
265+
{
266+
Key = 5,
267+
Version = "1.0.0",
268+
PackageRegistration = registrationCedillaNonLatin,
269+
VulnerablePackageRanges = new List<VulnerablePackageVersionRange>
270+
{
271+
versionRangeCriticalCedillaNonLatin
272+
}
273+
};
274+
var packageCedillaNonLatin2_100 = new Package
275+
{
276+
Key = 5,
277+
Version = "1.0.0",
278+
PackageRegistration = registrationCedillaNonLatin2,
279+
VulnerablePackageRanges = new List<VulnerablePackageVersionRange>
280+
{
281+
versionRangeCriticalCedillaNonLatin2
282+
}
283+
};
166284

167285
versionRangeCriticalFoo.Packages = new List<Package> { packageFoo111 };
168286
versionRangeModerateFoo.Packages = new List<Package> { packageFoo100, packageFoo110, packageFoo111, packageFoo112 };
169287
versionRangeCriticalBar.Packages = new List<Package> { packageBar100, packageBar110 };
288+
versionRangeCriticalBarNonLatin.Packages = new List<Package> { packageBarNonLatin100, packageBarNonLatin110 };
289+
versionRangeCriticalCedillaNonLatin.Packages = new List<Package> { packageCedillaNonLatin100 };
290+
versionRangeCriticalCedillaNonLatin2.Packages = new List<Package> { packageCedillaNonLatin2_100 };
170291

171-
var vulnerableRangeList = new List<VulnerablePackageVersionRange> { versionRangeCriticalFoo, versionRangeModerateFoo, versionRangeCriticalBar }.AsQueryable();
292+
var vulnerableRangeList = new List<VulnerablePackageVersionRange> { versionRangeCriticalFoo, versionRangeModerateFoo, versionRangeCriticalBar,
293+
versionRangeCriticalBarNonLatin, versionRangeCriticalCedillaNonLatin, versionRangeCriticalCedillaNonLatin2 }
294+
.AsQueryable();
172295
var vulnerableRangeDbSet = new Mock<DbSet<VulnerablePackageVersionRange>>();
173296

174297
// boilerplate mock DbSet redirects:

0 commit comments

Comments
 (0)