Skip to content

Commit e7cc39d

Browse files
authored
Putting self-contained icon upload behind feature flag (#7116)
* Feature flagged icon upload. Updated tests to accomodate. Fixed the misreported nuspec file size test.
1 parent 7f4c4f0 commit e7cc39d

15 files changed

Lines changed: 294 additions & 141 deletions

src/NuGetGallery/Controllers/ApiController.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -620,7 +620,7 @@ await PackageDeleteService.HardDeletePackagesAsync(
620620
}
621621

622622
// Perform all the validations we can before adding the package to the entity context.
623-
var beforeValidationResult = await PackageUploadService.ValidateBeforeGeneratePackageAsync(packageToPush, packageMetadata);
623+
var beforeValidationResult = await PackageUploadService.ValidateBeforeGeneratePackageAsync(packageToPush, packageMetadata, currentUser);
624624
var beforeValidationActionResult = GetActionResultOrNull(beforeValidationResult);
625625
if (beforeValidationActionResult != null)
626626
{

src/NuGetGallery/Controllers/PackagesController.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -274,7 +274,7 @@ private async Task<ActionResult> UploadPackageInternal(SubmitPackageRequest mode
274274
PackageMetadata packageMetadata,
275275
User currentUser)
276276
{
277-
var validationResult = await _packageUploadService.ValidateBeforeGeneratePackageAsync(packageArchiveReader, packageMetadata);
277+
var validationResult = await _packageUploadService.ValidateBeforeGeneratePackageAsync(packageArchiveReader, packageMetadata, currentUser);
278278
var validationErrorMessage = GetErrorMessageOrNull(validationResult);
279279
if (validationErrorMessage != null)
280280
{
@@ -644,7 +644,7 @@ private async Task<PackageContentData> ValidateAndProcessPackageContents(User cu
644644

645645
if (!isSymbolsPackageUpload)
646646
{
647-
var validationResult = await _packageUploadService.ValidateBeforeGeneratePackageAsync(packageArchiveReader, packageMetadata);
647+
var validationResult = await _packageUploadService.ValidateBeforeGeneratePackageAsync(packageArchiveReader, packageMetadata, currentUser);
648648
var validationJsonResult = GetJsonResultOrNull(validationResult);
649649
if (validationJsonResult != null)
650650
{
@@ -2325,7 +2325,7 @@ protected virtual async Task<JsonResult> VerifyPackageInternal(
23252325
}
23262326

23272327
// Perform all the validations we can before adding the package to the entity context.
2328-
var beforeValidationResult = await _packageUploadService.ValidateBeforeGeneratePackageAsync(packageArchiveReader, packageMetadata);
2328+
var beforeValidationResult = await _packageUploadService.ValidateBeforeGeneratePackageAsync(packageArchiveReader, packageMetadata, currentUser);
23292329
var beforeValidationJsonResult = GetJsonResultOrNull(beforeValidationResult);
23302330
if (beforeValidationJsonResult != null)
23312331
{

src/NuGetGallery/Services/FeatureFlagService.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ public class FeatureFlagService : IFeatureFlagService
1515
// Typosquatting detection
1616
private const string TyposquattingFeatureName = GalleryPrefix + "Typosquatting";
1717
private const string TyposquattingFlightName = GalleryPrefix + "TyposquattingFlight";
18+
private const string EmbeddedIconFlightName = GalleryPrefix + "EmbeddedIcons";
1819

1920
private const string PackagesAtomFeedFeatureName = GalleryPrefix + "PackagesAtomFeed";
2021

@@ -48,6 +49,11 @@ public bool IsManageDeprecationEnabled(User user)
4849
return _client.IsEnabled(ManageDeprecationFeatureName, user, defaultValue: false);
4950
}
5051

52+
public bool AreEmbeddedIconsEnabled(User user)
53+
{
54+
return _client.IsEnabled(EmbeddedIconFlightName, user, defaultValue: false);
55+
}
56+
5157
private bool IsEnabled(string flight, User user, bool defaultValue)
5258
{
5359
return _client.IsEnabled(flight, user, defaultValue);

src/NuGetGallery/Services/IFeatureFlagService.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,5 +42,10 @@ public interface IFeatureFlagService
4242
/// </summary>
4343
/// <returns></returns>
4444
bool IsSearchCircuitBreakerEnabled();
45+
46+
/// <summary>
47+
/// Whether the user is allowed to publish packages with an embedded icon.
48+
/// </summary>
49+
bool AreEmbeddedIconsEnabled(User user);
4550
}
4651
}

src/NuGetGallery/Services/IPackageUploadService.cs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,13 @@ public interface IPackageUploadService
1818
/// UI package upload.
1919
/// </summary>
2020
/// <param name="nuGetPackage">The package archive reader.</param>
21+
/// <param name="packageMetadata">The package metadata.</param>
22+
/// <param name="currentUser">The user who pushed the package.</param>
2123
/// <returns>The package validation result.</returns>
22-
Task<PackageValidationResult> ValidateBeforeGeneratePackageAsync(PackageArchiveReader nuGetPackage, PackageMetadata packageMetadata);
24+
Task<PackageValidationResult> ValidateBeforeGeneratePackageAsync(
25+
PackageArchiveReader nuGetPackage,
26+
PackageMetadata packageMetadata,
27+
User currentUser);
2328

2429
Task<Package> GeneratePackageAsync(
2530
string id,

src/NuGetGallery/Services/PackageUploadService.cs

Lines changed: 29 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ public class PackageUploadService : IPackageUploadService
4747
private const long MaxAllowedLicenseLengthForUploading = 1024 * 1024; // 1 MB
4848
private const int MaxAllowedLicenseNodeValueLength = 500;
4949
private const string LicenseNodeName = "license";
50+
private const string IconNodeName = "icon";
5051
private const string AllowedLicenseVersion = "1.0.0";
5152
private const string Unlicensed = "UNLICENSED";
5253

@@ -60,6 +61,7 @@ public class PackageUploadService : IPackageUploadService
6061
private readonly ITelemetryService _telemetryService;
6162
private readonly ICoreLicenseFileService _coreLicenseFileService;
6263
private readonly IDiagnosticsSource _trace;
64+
private readonly IFeatureFlagService _featureFlagService;
6365

6466
public PackageUploadService(
6567
IPackageService packageService,
@@ -71,7 +73,8 @@ public PackageUploadService(
7173
ITyposquattingService typosquattingService,
7274
ITelemetryService telemetryService,
7375
ICoreLicenseFileService coreLicenseFileService,
74-
IDiagnosticsService diagnosticsService)
76+
IDiagnosticsService diagnosticsService,
77+
IFeatureFlagService featureFlagService)
7578
{
7679
_packageService = packageService ?? throw new ArgumentNullException(nameof(packageService));
7780
_packageFileService = packageFileService ?? throw new ArgumentNullException(nameof(packageFileService));
@@ -87,9 +90,13 @@ public PackageUploadService(
8790
throw new ArgumentNullException(nameof(diagnosticsService));
8891
}
8992
_trace = diagnosticsService.GetSource(nameof(PackageUploadService));
93+
_featureFlagService = featureFlagService ?? throw new ArgumentNullException(nameof(featureFlagService));
9094
}
9195

92-
public async Task<PackageValidationResult> ValidateBeforeGeneratePackageAsync(PackageArchiveReader nuGetPackage, PackageMetadata packageMetadata)
96+
public async Task<PackageValidationResult> ValidateBeforeGeneratePackageAsync(
97+
PackageArchiveReader nuGetPackage,
98+
PackageMetadata packageMetadata,
99+
User currentUser)
93100
{
94101
var warnings = new List<IValidationMessage>();
95102

@@ -125,7 +132,7 @@ public async Task<PackageValidationResult> ValidateBeforeGeneratePackageAsync(Pa
125132
return result;
126133
}
127134

128-
result = await CheckLicenseMetadataAsync(nuGetPackage, warnings);
135+
result = await CheckLicenseMetadataAsync(nuGetPackage, warnings, currentUser);
129136
if (result != null)
130137
{
131138
_telemetryService.TrackLicenseValidationFailure();
@@ -135,13 +142,9 @@ public async Task<PackageValidationResult> ValidateBeforeGeneratePackageAsync(Pa
135142
return PackageValidationResult.AcceptedWithWarnings(warnings);
136143
}
137144

138-
private async Task<PackageValidationResult> CheckLicenseMetadataAsync(PackageArchiveReader nuGetPackage, List<IValidationMessage> warnings)
145+
private async Task<PackageValidationResult> CheckLicenseMetadataAsync(PackageArchiveReader nuGetPackage, List<IValidationMessage> warnings, User user)
139146
{
140-
LicenseCheckingNuspecReader nuspecReader = null;
141-
using (var nuspec = nuGetPackage.GetNuspec())
142-
{
143-
nuspecReader = new LicenseCheckingNuspecReader(nuspec);
144-
}
147+
var nuspecReader = GetNuspecReader(nuGetPackage);
145148

146149
var licenseElement = nuspecReader.LicenseElement;
147150

@@ -184,6 +187,12 @@ private async Task<PackageValidationResult> CheckLicenseMetadataAsync(PackageArc
184187
var licenseMetadata = nuspecReader.GetLicenseMetadata();
185188
var licenseDeprecationUrl = GetExpectedLicenseUrl(licenseMetadata);
186189

190+
// TODO: move out when full blown validation is implemented https://github.com/nuget/nugetgallery/issues/7063
191+
if (nuspecReader.IconExists && !_featureFlagService.AreEmbeddedIconsEnabled(user))
192+
{
193+
return PackageValidationResult.Invalid(Strings.UploadPackage_EmbeddedIconNotAccepted);
194+
}
195+
187196
if (licenseMetadata == null)
188197
{
189198
if (string.IsNullOrWhiteSpace(licenseUrl))
@@ -329,6 +338,14 @@ private async Task<PackageValidationResult> CheckLicenseMetadataAsync(PackageArc
329338
return null;
330339
}
331340

341+
private static UserContentEnabledNuspecReader GetNuspecReader(PackageArchiveReader nuGetPackage)
342+
{
343+
using (var nuspec = nuGetPackage.GetNuspec())
344+
{
345+
return new UserContentEnabledNuspecReader(nuspec);
346+
}
347+
}
348+
332349
private bool IsMalformedDeprecationUrl(string licenseUrl)
333350
{
334351
// nuget.exe 4.9.0 and its dotnet and msbuild counterparts encode spaces as "+"
@@ -435,14 +452,15 @@ private static string GetLicenseType(XElement licenseElement)
435452
=> licenseElement
436453
.GetOptionalAttributeValue("type");
437454

438-
private class LicenseCheckingNuspecReader : NuspecReader
455+
private class UserContentEnabledNuspecReader : NuspecReader
439456
{
440-
public LicenseCheckingNuspecReader(Stream stream)
457+
public UserContentEnabledNuspecReader(Stream stream)
441458
: base(stream)
442459
{
443460
}
444461

445462
public XElement LicenseElement => MetadataNode.Element(MetadataNode.Name.Namespace + LicenseNodeName);
463+
public bool IconExists => MetadataNode.Element(MetadataNode.Name.Namespace + IconNodeName) != null;
446464
}
447465

448466
private async Task<PackageValidationResult> CheckPackageEntryCountAsync(

src/NuGetGallery/Strings.Designer.cs

Lines changed: 9 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/NuGetGallery/Strings.resx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1118,4 +1118,7 @@ The {1} Team</value>
11181118
<data name="SymbolsPackage_NoSymbols" xml:space="preserve">
11191119
<value>The package does not contain any symbol (.pdb) files.</value>
11201120
</data>
1121+
<data name="UploadPackage_EmbeddedIconNotAccepted" xml:space="preserve">
1122+
<value>The &lt;icon&gt; element is not currently supported.</value>
1123+
</data>
11211124
</root>

tests/NuGetGallery.Core.Facts/TestUtils/PackageServiceUtility.cs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,9 @@ public static MemoryStream CreateNuGetPackageStream(string id = "theId",
126126
Func<string> getCustomNuspecNodes = null,
127127
string licenseExpression = null,
128128
string licenseFilename = null,
129-
byte[] licenseFileContents = null)
129+
byte[] licenseFileContents = null,
130+
string iconFilename = null,
131+
byte[] iconFileBinaryContents = null)
130132
{
131133
if (packageDependencyGroups == null)
132134
{
@@ -188,7 +190,9 @@ public static MemoryStream CreateNuGetPackageStream(string id = "theId",
188190
getCustomNuspecNodes: getCustomNuspecNodes,
189191
licenseExpression: licenseExpression,
190192
licenseFilename: licenseFilename,
191-
licenseFileContents: licenseFileContents);
193+
licenseFileContents: licenseFileContents,
194+
iconFilename: iconFilename,
195+
iconFileContents: iconFileBinaryContents);
192196
}
193197

194198
public static PackageArchiveReader CreateArchiveReader(Stream stream)

tests/NuGetGallery.Core.Facts/TestUtils/TestPackage.cs

Lines changed: 35 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,8 @@ public static void WriteNuspec(
4141
RepositoryMetadata repositoryMetadata = null,
4242
Func<string> getCustomNodes = null,
4343
string licenseExpression = null,
44-
string licenseFilename = null)
44+
string licenseFilename = null,
45+
string iconFilename = null)
4546
{
4647
var fullNuspec = (@"<?xml version=""1.0""?>
4748
<package xmlns=""http://schemas.microsoft.com/packaging/2011/08/nuspec.xsd"">
@@ -60,6 +61,7 @@ public static void WriteNuspec(
6061
<releaseNotes>" + (releaseNotes ?? string.Empty) + @"</releaseNotes>
6162
<licenseUrl>" + (licenseUrl?.AbsoluteUri ?? string.Empty) + @"</licenseUrl>
6263
" + WriteLicense(licenseExpression, licenseFilename) + @"
64+
" + WriteIcon(iconFilename) + @"
6365
<projectUrl>" + (projectUrl?.ToString() ?? string.Empty) + @"</projectUrl>
6466
<iconUrl>" + (iconUrl?.ToString() ?? string.Empty) + @"</iconUrl>
6567
<packageTypes>" + WritePackageTypes(packageTypes) + @"</packageTypes>
@@ -102,6 +104,16 @@ private static string WriteLicense(string licenseExpression, string licenseFilen
102104
return stringBuilder.ToString();
103105
}
104106

107+
private static string WriteIcon(string iconFilename)
108+
{
109+
if (iconFilename != null)
110+
{
111+
return $"<icon>{iconFilename}</icon>";
112+
}
113+
114+
return string.Empty;
115+
}
116+
105117
private static string WriteRepositoryMetadata(RepositoryMetadata repositoryMetadata)
106118
{
107119
return repositoryMetadata == null
@@ -198,7 +210,9 @@ public static MemoryStream CreateTestPackageStream(
198210
Func<string> getCustomNuspecNodes = null,
199211
string licenseExpression = null,
200212
string licenseFilename = null,
201-
byte[] licenseFileContents = null)
213+
byte[] licenseFileContents = null,
214+
string iconFilename = null,
215+
byte[] iconFileContents = null)
202216
{
203217
return CreateTestPackageStream(packageArchive =>
204218
{
@@ -208,19 +222,11 @@ public static MemoryStream CreateTestPackageStream(
208222
WriteNuspec(stream, true, id, version, title, summary, authors, owners, description, tags, language,
209223
copyright, releaseNotes, minClientVersion, licenseUrl, projectUrl, iconUrl,
210224
requireLicenseAcceptance, packageDependencyGroups, packageTypes, isSymbolPackage, repositoryMetadata,
211-
getCustomNuspecNodes, licenseExpression, licenseFilename);
225+
getCustomNuspecNodes, licenseExpression, licenseFilename, iconFilename);
212226
}
213227

214-
if (licenseFileContents != null && licenseFilename != null)
215-
{
216-
// enforce directory separators the same way as the client (see PackageArchiveReader.GetStream)
217-
licenseFilename = PathUtility.StripLeadingDirectorySeparators(licenseFilename);
218-
var licenseEntry = packageArchive.CreateEntry(licenseFilename, CompressionLevel.Fastest);
219-
using (var licenseStream = licenseEntry.Open())
220-
{
221-
licenseStream.Write(licenseFileContents, 0, licenseFileContents.Length);
222-
}
223-
}
228+
licenseFilename = AddBinaryFile(packageArchive, licenseFilename, licenseFileContents);
229+
iconFilename = AddBinaryFile(packageArchive, iconFilename, iconFileContents);
224230

225231
if (populatePackage != null)
226232
{
@@ -229,6 +235,22 @@ public static MemoryStream CreateTestPackageStream(
229235
}, desiredTotalEntryCount);
230236
}
231237

238+
private static string AddBinaryFile(ZipArchive archive, string filename, byte[] fileContents)
239+
{
240+
if (fileContents != null && filename != null)
241+
{
242+
// enforce directory separators the same way as the client (see PackageArchiveReader.GetStream)
243+
filename = PathUtility.StripLeadingDirectorySeparators(filename);
244+
var fileEntry = archive.CreateEntry(filename, CompressionLevel.Fastest);
245+
using (var fileStream = fileEntry.Open())
246+
{
247+
fileStream.Write(fileContents, 0, fileContents.Length);
248+
}
249+
}
250+
251+
return filename;
252+
}
253+
232254
public static MemoryStream CreateTestSymbolPackageStream(string id = "theId", string version = "1.0.42", Action<ZipArchive> populatePackage = null)
233255
{
234256
var packageTypes = new List<ClientPackageType>();

0 commit comments

Comments
 (0)