Skip to content

Commit 5c62798

Browse files
authored
Allow if conflict with package from same owner (#10163)
1 parent 3195321 commit 5c62798

7 files changed

Lines changed: 131 additions & 22 deletions

src/NuGetGallery/NuGetGallery.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -352,6 +352,7 @@
352352
<Compile Include="RequestModels\UpdateListedRequest.cs" />
353353
<Compile Include="Services\ListedVerb.cs" />
354354
<Compile Include="Services\MissingLicenseValidationMessageV2.cs" />
355+
<Compile Include="Services\NormalizedPackageIdInfo.cs" />
355356
<Compile Include="Services\NullTyposquattingService.cs" />
356357
<Compile Include="Services\UploadPackageMissingReadme.cs" />
357358
<Compile Include="Services\MissingLicenseValidationMessage.cs" />

src/NuGetGallery/Services/ITyposquattingCheckListCacheService.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright (c) .NET Foundation. All rights reserved.
1+
// Copyright (c) .NET Foundation. All rights reserved.
22
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
33

44
using System;
@@ -17,6 +17,6 @@ public interface ITyposquattingCheckListCacheService
1717
/// <param name="checkListLength">The length of the checklist for typosquatting</param>
1818
/// <param name="checkListExpireTime">The expire time for checklist caching</param>
1919
/// <param name="packageService">The package service for checklist retrieval from database</param>
20-
IReadOnlyCollection<string> GetTyposquattingCheckList(int checkListLength, TimeSpan checkListExpireTime, IPackageService packageService);
20+
IReadOnlyCollection<NormalizedPackageIdInfo> GetTyposquattingCheckList(int checkListLength, TimeSpan checkListExpireTime, IPackageService packageService);
2121
}
2222
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using System;
5+
6+
namespace NuGetGallery
7+
{
8+
public sealed class NormalizedPackageIdInfo
9+
{
10+
public string OriginalId { get; }
11+
public string NormalizedId { get; }
12+
13+
public NormalizedPackageIdInfo(string originalId, string normalizedId)
14+
{
15+
OriginalId = originalId ?? throw new ArgumentNullException(nameof(originalId));
16+
NormalizedId = normalizedId ?? throw new ArgumentNullException(nameof(normalizedId));
17+
}
18+
19+
}
20+
}

src/NuGetGallery/Services/TyposquattingCheckListCacheService.cs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright (c) .NET Foundation. All rights reserved.
1+
// Copyright (c) .NET Foundation. All rights reserved.
22
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
33

44
using System;
@@ -12,7 +12,7 @@ public class TyposquattingCheckListCacheService : ITyposquattingCheckListCacheSe
1212
private readonly object Locker = new object();
1313
private readonly ITyposquattingServiceHelper _typosquattingServiceHelper;
1414

15-
private List<string> Cache;
15+
private List<NormalizedPackageIdInfo> Cache;
1616
private DateTime LastRefreshTime;
1717

1818
private int TyposquattingCheckListConfiguredLength;
@@ -24,7 +24,7 @@ public TyposquattingCheckListCacheService(ITyposquattingServiceHelper typosquatt
2424
_typosquattingServiceHelper = typosquattingServiceHelper;
2525
}
2626

27-
public IReadOnlyCollection<string> GetTyposquattingCheckList(int checkListConfiguredLength, TimeSpan checkListExpireTime, IPackageService packageService)
27+
public IReadOnlyCollection<NormalizedPackageIdInfo> GetTyposquattingCheckList(int checkListConfiguredLength, TimeSpan checkListExpireTime, IPackageService packageService)
2828
{
2929
if (packageService == null)
3030
{
@@ -54,7 +54,7 @@ public IReadOnlyCollection<string> GetTyposquattingCheckList(int checkListConfig
5454
.ToList();
5555

5656
Cache = cachedPackages
57-
.Select(pr => _typosquattingServiceHelper.NormalizeString(pr))
57+
.Select(pr => new NormalizedPackageIdInfo(originalId: pr, normalizedId: _typosquattingServiceHelper.NormalizeString(pr)))
5858
.ToList();
5959

6060
LastRefreshTime = DateTime.UtcNow;
@@ -75,4 +75,4 @@ private bool IsCheckListCacheExpired(TimeSpan checkListExpireTime)
7575
return DateTime.UtcNow >= LastRefreshTime.Add(checkListExpireTime);
7676
}
7777
}
78-
}
78+
}

src/NuGetGallery/Services/TyposquattingService.cs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright (c) .NET Foundation. All rights reserved.
1+
// Copyright (c) .NET Foundation. All rights reserved.
22
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
33

44
using System;
@@ -60,20 +60,20 @@ public bool IsUploadedPackageIdTyposquatting(string uploadedPackageId, User uplo
6060

6161
var totalTimeStopwatch = Stopwatch.StartNew();
6262
var checklistRetrievalStopwatch = Stopwatch.StartNew();
63-
63+
6464
// It must be normalized during initial list creation
65-
var normalizedPackageIdsCheckList = _typosquattingCheckListCacheService.GetTyposquattingCheckList(checkListConfiguredLength, checkListExpireTimeInHours, _packageService);
65+
IReadOnlyCollection<NormalizedPackageIdInfo> normalizedPackageIdsCheckList = _typosquattingCheckListCacheService.GetTyposquattingCheckList(checkListConfiguredLength, checkListExpireTimeInHours, _packageService);
6666
checklistRetrievalStopwatch.Stop();
6767

6868
_telemetryService.TrackMetricForTyposquattingChecklistRetrievalTime(uploadedPackageId, checklistRetrievalStopwatch.Elapsed);
6969

7070
var algorithmProcessingStopwatch = Stopwatch.StartNew();
7171
var collisionIds = new ConcurrentBag<string>();
72-
Parallel.ForEach(normalizedPackageIdsCheckList, (normalizedPackageId, loopState) =>
72+
Parallel.ForEach(normalizedPackageIdsCheckList, (normalizedPackageIdPair, loopState) =>
7373
{
74-
if (_typosquattingServiceHelper.IsDistanceLessThanOrEqualToThresholdWithNormalizedPackageId(uploadedPackageId, normalizedPackageId))
74+
if (_typosquattingServiceHelper.IsDistanceLessThanOrEqualToThresholdWithNormalizedPackageId(uploadedPackageId, normalizedPackageIdPair.NormalizedId))
7575
{
76-
collisionIds.Add(normalizedPackageId);
76+
collisionIds.Add(normalizedPackageIdPair.OriginalId);
7777
}
7878
});
7979
algorithmProcessingStopwatch.Stop();
@@ -134,4 +134,4 @@ public bool IsUploadedPackageIdTyposquatting(string uploadedPackageId, User uplo
134134
return wasUploadBlocked;
135135
}
136136
}
137-
}
137+
}

tests/NuGetGallery.Facts/Services/TyposquattingCheckListCacheServiceFacts.cs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright (c) .NET Foundation. All rights reserved.
1+
// Copyright (c) .NET Foundation. All rights reserved.
22
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
33

44
using System;
@@ -44,6 +44,8 @@ public async Task WhenGettingCheckListFromMultipleThreadsItIsInitializedOnce()
4444
.Returns(PacakgeRegistrationsList);
4545

4646
var mockTyposquattingServiceHelper = new Mock<ITyposquattingServiceHelper>();
47+
mockTyposquattingServiceHelper.Setup(helper => helper.NormalizeString(It.IsAny<string>()))
48+
.Returns((string str) => str.ToLower());
4749

4850
var newService = new TyposquattingCheckListCacheService(mockTyposquattingServiceHelper.Object);
4951

@@ -76,6 +78,8 @@ public void WhenExceedExpireTimeRefreshCheckListCache()
7678
.Returns(PacakgeRegistrationsList);
7779

7880
var mockTyposquattingServiceHelper = new Mock<ITyposquattingServiceHelper>();
81+
mockTyposquattingServiceHelper.Setup(helper => helper.NormalizeString(It.IsAny<string>()))
82+
.Returns((string str) => str.ToLower());
7983
var newService = new TyposquattingCheckListCacheService(mockTyposquattingServiceHelper.Object);
8084

8185
// Act
@@ -99,6 +103,8 @@ public void WhenNotEqualConfiguredLengthRefreshCheckListCache()
99103
.Returns(PacakgeRegistrationsList);
100104

101105
var mockTyposquattingServiceHelper = new Mock<ITyposquattingServiceHelper>();
106+
mockTyposquattingServiceHelper.Setup(helper => helper.NormalizeString(It.IsAny<string>()))
107+
.Returns((string str) => str.ToLower());
102108
var newService = new TyposquattingCheckListCacheService(mockTyposquattingServiceHelper.Object);
103109

104110
// Act
@@ -168,4 +174,4 @@ public void CheckNullPackageService()
168174
Assert.Equal("packageService", exception.ParamName);
169175
}
170176
}
171-
}
177+
}

tests/NuGetGallery.Facts/Services/TyposquattingServiceFacts.cs

Lines changed: 88 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright (c) .NET Foundation. All rights reserved.
1+
// Copyright (c) .NET Foundation. All rights reserved.
22
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
33

44
using System;
@@ -24,7 +24,7 @@ public class TyposquattingServiceFacts
2424
"System.Spatial"
2525
};
2626

27-
private static IQueryable<PackageRegistration> PacakgeRegistrationsList = Enumerable.Range(0, _packageIds.Count()).Select(i =>
27+
private static IQueryable<PackageRegistration> PackageRegistrationsList = Enumerable.Range(0, _packageIds.Count()).Select(i =>
2828
new PackageRegistration()
2929
{
3030
Id = _packageIds[i],
@@ -48,7 +48,7 @@ private static ITyposquattingService CreateService(
4848
packageService = new Mock<IPackageService>();
4949
packageService
5050
.Setup(x => x.GetAllPackageRegistrations())
51-
.Returns(PacakgeRegistrationsList);
51+
.Returns(PackageRegistrationsList);
5252
}
5353

5454
if (contentObjectService == null)
@@ -88,10 +88,15 @@ private static ITyposquattingService CreateService(
8888

8989
if (typosquattingCheckListCacheService == null)
9090
{
91+
List<NormalizedPackageIdInfo> normalizedPackageIdInfos = PackageRegistrationsList
92+
.ToList()
93+
.Select(pr => new NormalizedPackageIdInfo(pr.Id, pr.Id))
94+
.ToList();
95+
9196
typosquattingCheckListCacheService = new Mock<ITyposquattingCheckListCacheService>();
9297
typosquattingCheckListCacheService
9398
.Setup(x => x.GetTyposquattingCheckList(It.IsAny<int>(), It.IsAny<TimeSpan>(), It.IsAny<IPackageService>()))
94-
.Returns(PacakgeRegistrationsList.Select(pr => pr.Id).ToList());
99+
.Returns(normalizedPackageIdInfos);
95100
}
96101

97102
return new TyposquattingService(
@@ -220,7 +225,7 @@ public void CheckTyposquattingEmptyChecklist()
220225
var mockTyposquattingCheckListCacheService = new Mock<ITyposquattingCheckListCacheService>();
221226
mockTyposquattingCheckListCacheService
222227
.Setup(x => x.GetTyposquattingCheckList(It.IsAny<int>(), It.IsAny<TimeSpan>(), It.IsAny<IPackageService>()))
223-
.Returns(new List<string>());
228+
.Returns(new List<NormalizedPackageIdInfo>());
224229

225230
var newService = CreateService(packageService: mockPackageService, typosquattingCheckListCacheService: mockTyposquattingCheckListCacheService);
226231

@@ -315,6 +320,83 @@ public void CheckIsTyposquattingBlockUserNotEnabled()
315320
.Verify(f => f.IsTyposquattingEnabled(_uploadedPackageOwner), Times.Once);
316321
}
317322

323+
[Fact]
324+
public void CheckIsTyposquattingBlockDifferentOwnersUploadBlocked()
325+
{
326+
// Arrange
327+
var uploadedPackageId = "Microsoft_NetFramework_v1";
328+
string conflictingPackageId = "microsoft_netframework_v1";
329+
User conflictingPackageOwner = PackageRegistrationsList
330+
.Single(pr => pr.Id == conflictingPackageId)
331+
.Owners
332+
.First();
333+
334+
// Make sure they have different owners.
335+
Assert.NotEqual(_uploadedPackageOwner.Key, conflictingPackageOwner.Key);
336+
337+
var featureFlagService = new Mock<IFeatureFlagService>();
338+
featureFlagService
339+
.Setup(f => f.IsTyposquattingEnabled())
340+
.Returns(true);
341+
featureFlagService
342+
.Setup(f => f.IsTyposquattingEnabled(_uploadedPackageOwner))
343+
.Returns(true);
344+
345+
var newService = CreateService(featureFlagService: featureFlagService);
346+
347+
// Act
348+
349+
var typosquattingCheckResult = newService.IsUploadedPackageIdTyposquatting(uploadedPackageId, _uploadedPackageOwner, out List<string> typosquattingCheckCollisionIds);
350+
351+
// Assert
352+
Assert.True(typosquattingCheckResult);
353+
Assert.Single(typosquattingCheckCollisionIds);
354+
Assert.Equal(conflictingPackageId, typosquattingCheckCollisionIds[0]);
355+
356+
featureFlagService
357+
.Verify(f => f.IsTyposquattingEnabled(), Times.Once);
358+
featureFlagService
359+
.Verify(f => f.IsTyposquattingEnabled(_uploadedPackageOwner), Times.Once);
360+
}
361+
362+
[Fact]
363+
public void CheckIsTyposquattingBlockSameOwnerUploadNotBlocked()
364+
{
365+
// Arrange
366+
var uploadedPackageId = "Microsoft_NetFramework_v1";
367+
string conflictingPackageId = "microsoft_netframework_v1";
368+
369+
var featureFlagService = new Mock<IFeatureFlagService>();
370+
371+
// Use same owner for both packages.
372+
User samePackageOwner = PackageRegistrationsList
373+
.Single(pr => pr.Id == conflictingPackageId)
374+
.Owners
375+
.First();
376+
377+
featureFlagService
378+
.Setup(f => f.IsTyposquattingEnabled())
379+
.Returns(true);
380+
featureFlagService
381+
.Setup(f => f.IsTyposquattingEnabled(samePackageOwner))
382+
.Returns(true);
383+
384+
var newService = CreateService(featureFlagService: featureFlagService);
385+
386+
// Act
387+
388+
var typosquattingCheckResult = newService.IsUploadedPackageIdTyposquatting(uploadedPackageId, samePackageOwner, out List<string> typosquattingCheckCollisionIds);
389+
390+
// Assert
391+
Assert.False(typosquattingCheckResult);
392+
Assert.Empty(typosquattingCheckCollisionIds);
393+
394+
featureFlagService
395+
.Verify(f => f.IsTyposquattingEnabled(), Times.Once);
396+
featureFlagService
397+
.Verify(f => f.IsTyposquattingEnabled(samePackageOwner), Times.Once);
398+
}
399+
318400
[Fact]
319401
public void CheckTelemetryServiceLogOriginalUploadedPackageId()
320402
{
@@ -347,4 +429,4 @@ public void CheckTelemetryServiceLogOriginalUploadedPackageId()
347429
Times.Once);
348430
}
349431
}
350-
}
432+
}

0 commit comments

Comments
 (0)