Skip to content

Commit 3b705c6

Browse files
authored
Typosquatting: finish configuration file and feature flags (#6390)
* Typosquatting: finish configuration file and feature flags * Update unit tests * Update * update unit tests
1 parent 77c0b16 commit 3b705c6

10 files changed

Lines changed: 91 additions & 24 deletions

File tree

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"packageIdChecklistLength": 20000,
3+
"isCheckEnabled": false,
4+
"isBlockUsersEnabled": false
5+
}

src/NuGetGallery/App_Start/OwinStartup.cs

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -63,14 +63,17 @@ public static void Configuration(IAppBuilder app)
6363

6464
// Configure machine key for session persistence across slots
6565
SessionPersistence.Setup(config);
66-
6766
// Refresh the content for the ContentObjectService to guarantee it has loaded the latest configuration on startup.
68-
if (config.Current.IsHosted)
67+
var contentObjectService = dependencyResolver.GetService<IContentObjectService>();
68+
HostingEnvironment.QueueBackgroundWorkItem(async token =>
6969
{
70-
var contentObjectService = dependencyResolver.GetService<IContentObjectService>();
71-
HostingEnvironment.QueueBackgroundWorkItem(async cancellationToken => await contentObjectService.Refresh());
72-
}
73-
70+
while (!token.IsCancellationRequested)
71+
{
72+
await contentObjectService.Refresh();
73+
await Task.Delay(ContentObjectService.RefreshInterval, token);
74+
}
75+
});
76+
7477
// Setup telemetry
7578
var instrumentationKey = config.Current.AppInsightsInstrumentationKey;
7679
if (!string.IsNullOrEmpty(instrumentationKey))

src/NuGetGallery/Constants.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@ public static class ContentNames
104104
public static readonly string LoginDiscontinuationConfiguration = "Login-Discontinuation-Configuration";
105105
public static readonly string CertificatesConfiguration = "Certificates-Configuration";
106106
public static readonly string SymbolsConfiguration = "Symbols-Configuration";
107+
public static readonly string TyposquattingConfiguration = "Typosquatting-Configuration";
107108
}
108109

109110
public static class StatisticsDimensions

src/NuGetGallery/NuGetGallery.csproj

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -395,12 +395,14 @@
395395
<Compile Include="Services\BackgroundMessageService.cs" />
396396
<Compile Include="Services\ISymbolPackageUploadService.cs" />
397397
<Compile Include="Services\ISymbolPackageFileService.cs" />
398+
<Compile Include="Services\ITyposquattingConfiguration.cs" />
398399
<Compile Include="Services\ISymbolsConfiguration.cs" />
399400
<Compile Include="Services\ITyposquattingCheckService.cs" />
400401
<Compile Include="Services\ITyposquattingUserService.cs" />
401402
<Compile Include="Services\SymbolPackageUploadService.cs" />
402403
<Compile Include="Services\SymbolPackageFileService.cs" />
403404
<Compile Include="Services\SymbolPackageService.cs" />
405+
<Compile Include="Services\TyposquattingConfiguration.cs" />
404406
<Compile Include="Services\SymbolsConfiguration.cs" />
405407
<Compile Include="Services\CertificatesConfiguration.cs" />
406408
<Compile Include="Services\CertificateService.cs" />
@@ -1746,6 +1748,7 @@
17461748
<EmbeddedResource Include="Infrastructure\Elmah.SqlServer.sql" />
17471749
<EmbeddedResource Include="Infrastructure\AddPackageLicenseReport.sql" />
17481750
<Content Include="Public\robots.txt" />
1751+
<Content Include="App_Data\Files\Content\Typosquatting-Configuration.json" />
17491752
<None Include="Properties\PublishProfiles\nuget-staging-frontend.pubxml" />
17501753
<Content Include="Scripts\gallery\**\*" />
17511754
<Content Include="Web.config">

src/NuGetGallery/Services/ContentObjectService.cs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ namespace NuGetGallery
1010
{
1111
public class ContentObjectService : IContentObjectService
1212
{
13-
private const int RefreshIntervalMinutes = 5;
13+
public static TimeSpan RefreshInterval = TimeSpan.FromMinutes(5);
1414

1515
private readonly IContentService _contentService;
1616

@@ -21,11 +21,13 @@ public ContentObjectService(IContentService contentService)
2121
LoginDiscontinuationConfiguration = new LoginDiscontinuationConfiguration();
2222
CertificatesConfiguration = new CertificatesConfiguration();
2323
SymbolsConfiguration = new SymbolsConfiguration();
24+
TyposquattingConfiguration = new TyposquattingConfiguration();
2425
}
2526

2627
public ILoginDiscontinuationConfiguration LoginDiscontinuationConfiguration { get; set; }
2728
public ICertificatesConfiguration CertificatesConfiguration { get; set; }
2829
public ISymbolsConfiguration SymbolsConfiguration { get; set; }
30+
public ITyposquattingConfiguration TyposquattingConfiguration { get; set; }
2931

3032
public async Task Refresh()
3133
{
@@ -40,12 +42,16 @@ await Refresh<CertificatesConfiguration>(Constants.ContentNames.CertificatesConf
4042
SymbolsConfiguration =
4143
await Refresh<SymbolsConfiguration>(Constants.ContentNames.SymbolsConfiguration) ??
4244
new SymbolsConfiguration();
45+
46+
TyposquattingConfiguration =
47+
await Refresh<TyposquattingConfiguration>(Constants.ContentNames.TyposquattingConfiguration) ??
48+
new TyposquattingConfiguration();
4349
}
4450

4551
private async Task<T> Refresh<T>(string contentName)
4652
where T : class
4753
{
48-
var configString = (await _contentService.GetContentItemAsync(contentName, TimeSpan.FromMinutes(RefreshIntervalMinutes)))?.ToString();
54+
var configString = (await _contentService.GetContentItemAsync(contentName, RefreshInterval))?.ToString();
4955
if (string.IsNullOrEmpty(configString))
5056
{
5157
return null;

src/NuGetGallery/Services/IContentObjectService.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ public interface IContentObjectService
1111
ILoginDiscontinuationConfiguration LoginDiscontinuationConfiguration { get; }
1212
ICertificatesConfiguration CertificatesConfiguration { get; }
1313
ISymbolsConfiguration SymbolsConfiguration { get; }
14-
14+
ITyposquattingConfiguration TyposquattingConfiguration { get; }
1515
Task Refresh();
1616
}
1717
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
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+
namespace NuGetGallery.Services
5+
{
6+
public interface ITyposquattingConfiguration
7+
{
8+
int PackageIdChecklistLength { get; }
9+
bool IsCheckEnabled { get; }
10+
bool IsBlockUsersEnabled { get; }
11+
}
12+
}

src/NuGetGallery/Services/TyposquattingCheckService.cs

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,26 +11,26 @@ namespace NuGetGallery
1111
{
1212
public class TyposquattingCheckService : ITyposquattingCheckService
1313
{
14-
// TODO: Length of checklist will be saved in the configuration file.
15-
// https://github.com/NuGet/Engineering/issues/1645
16-
private const int TyposquattingCheckListLength = 20000;
17-
18-
// TODO: Threshold parameters will be saved in the configuration file.
19-
// https://github.com/NuGet/Engineering/issues/1645
2014
private static readonly IReadOnlyList<ThresholdInfo> ThresholdsList = new List<ThresholdInfo>
2115
{
2216
new ThresholdInfo (lowerBound: 0, upperBound: 30, threshold: 0),
2317
new ThresholdInfo (lowerBound: 30, upperBound: 50, threshold: 1),
24-
new ThresholdInfo (lowerBound: 50, upperBound: 121, threshold: 2)
18+
new ThresholdInfo (lowerBound: 50, upperBound: 129, threshold: 2)
2519
};
2620

21+
private static int TyposquattingCheckListLength;
22+
2723
private readonly ITyposquattingUserService _userTyposquattingService;
2824
private readonly IEntityRepository<PackageRegistration> _packageRegistrationRepository;
25+
private readonly IContentObjectService _contentObjectService;
2926

30-
public TyposquattingCheckService(ITyposquattingUserService typosquattingUserService, IEntityRepository<PackageRegistration> packageRegistrationRepository)
27+
public TyposquattingCheckService(ITyposquattingUserService typosquattingUserService, IEntityRepository<PackageRegistration> packageRegistrationRepository, IContentObjectService contentObjectService)
3128
{
3229
_userTyposquattingService = typosquattingUserService ?? throw new ArgumentNullException(nameof(typosquattingUserService));
3330
_packageRegistrationRepository = packageRegistrationRepository ?? throw new ArgumentNullException(nameof(packageRegistrationRepository));
31+
_contentObjectService = contentObjectService ?? throw new ArgumentNullException(nameof(contentObjectService));
32+
33+
TyposquattingCheckListLength = _contentObjectService.TyposquattingConfiguration.PackageIdChecklistLength;
3434
}
3535

3636
public bool IsUploadedPackageIdTyposquatting(string uploadedPackageId, User uploadedPackageOwner)
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
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 Newtonsoft.Json;
5+
6+
namespace NuGetGallery.Services
7+
{
8+
public sealed class TyposquattingConfiguration : ITyposquattingConfiguration
9+
{
10+
private const int DefaultPackageIdCheckListLength = 20000;
11+
public int PackageIdChecklistLength { get; }
12+
public bool IsCheckEnabled { get; }
13+
public bool IsBlockUsersEnabled { get; }
14+
public TyposquattingConfiguration()
15+
: this(packageIdChecklistLength: DefaultPackageIdCheckListLength,
16+
isCheckEnabled: false,
17+
isBlockUsersEnabled: false)
18+
{
19+
}
20+
21+
[JsonConstructor]
22+
public TyposquattingConfiguration(
23+
int packageIdChecklistLength,
24+
bool isCheckEnabled,
25+
bool isBlockUsersEnabled)
26+
{
27+
PackageIdChecklistLength = packageIdChecklistLength;
28+
IsCheckEnabled = isCheckEnabled;
29+
IsBlockUsersEnabled = isBlockUsersEnabled;
30+
}
31+
}
32+
}

tests/NuGetGallery.Facts/Services/TyposquattingCheckServiceFacts.cs

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -32,12 +32,17 @@ public class TyposquattingCheckServiceFacts
3232

3333
private static Mock<ITyposquattingUserService> _typosquattingUserService = new Mock<ITyposquattingUserService>();
3434
private static Mock<IEntityRepository<PackageRegistration>> _packageRegistrationRepository = new Mock<IEntityRepository<PackageRegistration>>();
35+
private static Mock<IContentObjectService> _contentObjectService = new Mock<IContentObjectService>();
3536

3637
public TyposquattingCheckServiceFacts()
3738
{
3839
_packageRegistrationRepository
3940
.Setup(x => x.GetAll())
4041
.Returns(_pacakgeRegistrationsList.AsQueryable());
42+
43+
_contentObjectService
44+
.Setup(x => x.TyposquattingConfiguration.PackageIdChecklistLength)
45+
.Returns(20000);
4146
}
4247

4348
[Fact]
@@ -51,7 +56,7 @@ public void CheckNotTyposquattingByDifferentOwnersTest()
5156
.Setup(x => x.CanUserTyposquat(It.IsAny<string>(), It.IsAny<string>()))
5257
.Returns(false);
5358

54-
var newService = new TyposquattingCheckService(_typosquattingUserService.Object, _packageRegistrationRepository.Object);
59+
var newService = new TyposquattingCheckService(_typosquattingUserService.Object, _packageRegistrationRepository.Object, _contentObjectService.Object);
5560

5661
// Act
5762
var typosquattingCheckResult = newService.IsUploadedPackageIdTyposquatting(uploadedPackageId, uploadedPackageOwner);
@@ -72,7 +77,7 @@ public void CheckNotTyposquattingBySameOwnersTest()
7277
.Setup(x => x.CanUserTyposquat(It.IsAny<string>(), It.IsAny<string>()))
7378
.Returns(true);
7479

75-
var newService = new TyposquattingCheckService(_typosquattingUserService.Object, _packageRegistrationRepository.Object);
80+
var newService = new TyposquattingCheckService(_typosquattingUserService.Object, _packageRegistrationRepository.Object, _contentObjectService.Object);
7681

7782
// Act
7883
var typosquattingCheckResult = newService.IsUploadedPackageIdTyposquatting(uploadedPackageId, uploadedPackageOwner);
@@ -92,7 +97,7 @@ public void CheckTyposquattingByDifferentOwnersTest()
9297
.Setup(x => x.CanUserTyposquat(It.IsAny<string>(), It.IsAny<string>()))
9398
.Returns(false);
9499

95-
var newService = new TyposquattingCheckService(_typosquattingUserService.Object, _packageRegistrationRepository.Object);
100+
var newService = new TyposquattingCheckService(_typosquattingUserService.Object, _packageRegistrationRepository.Object, _contentObjectService.Object);
96101

97102
// Act
98103
var typosquattingCheckResult = newService.IsUploadedPackageIdTyposquatting(uploadedPackageId, uploadedPackageOwner);
@@ -108,7 +113,7 @@ public void CheckTyposquattingNullUploadedPackageId()
108113
var uploadedPackageOwner = new User();
109114
string uploadedPackageId = null;
110115

111-
var newService = new TyposquattingCheckService(_typosquattingUserService.Object, _packageRegistrationRepository.Object);
116+
var newService = new TyposquattingCheckService(_typosquattingUserService.Object, _packageRegistrationRepository.Object, _contentObjectService.Object);
112117

113118
// Act
114119
var exception = Assert.Throws<ArgumentNullException>(
@@ -125,7 +130,7 @@ public void CheckTyposquattingNullUploadedPackageOwner()
125130
User uploadedPackageOwner = null;
126131
var uploadedPackageId = "microsoft_netframework_v1";
127132

128-
var newService = new TyposquattingCheckService(_typosquattingUserService.Object, _packageRegistrationRepository.Object);
133+
var newService = new TyposquattingCheckService(_typosquattingUserService.Object, _packageRegistrationRepository.Object, _contentObjectService.Object);
129134

130135
// Act
131136
var exception = Assert.Throws<ArgumentNullException>(
@@ -142,7 +147,7 @@ public void CheckTyposquattingEmptyUploadedPackageId()
142147
var uploadedPackageOwner = new User();
143148
var uploadedPackageId = "";
144149

145-
var newService = new TyposquattingCheckService(_typosquattingUserService.Object, _packageRegistrationRepository.Object);
150+
var newService = new TyposquattingCheckService(_typosquattingUserService.Object, _packageRegistrationRepository.Object, _contentObjectService.Object);
146151

147152
// Act
148153
var typosquattingCheckResult = newService.IsUploadedPackageIdTyposquatting(uploadedPackageId, uploadedPackageOwner);
@@ -165,7 +170,7 @@ public void CheckTyposquattingEmptyChecklist()
165170
.Setup(x => x.GetAll())
166171
.Returns(new List<PackageRegistration>().AsQueryable());
167172

168-
var newService = new TyposquattingCheckService(_typosquattingUserService.Object, _packageRegistrationRepository.Object);
173+
var newService = new TyposquattingCheckService(_typosquattingUserService.Object, _packageRegistrationRepository.Object, _contentObjectService.Object);
169174

170175
// Act
171176
var typosquattingCheckResult = newService.IsUploadedPackageIdTyposquatting(uploadedPackageId, uploadedPackageOwner);

0 commit comments

Comments
 (0)