Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Embedded license file ingestion for Gallery #6580

Merged
merged 57 commits into from
Nov 8, 2018
Merged
Show file tree
Hide file tree
Changes from 30 commits
Commits
Show all changes
57 commits
Select commit Hold shift + click to select a range
8f797f4
WIP. Tests added. Started validation implementation.
agr Oct 12, 2018
5f79521
Removed the license expression tests as there is nothing to test yet.
agr Oct 16, 2018
4955880
Tests for saving and cleaning up license files
agr Oct 16, 2018
55a7fb0
Test fixes
agr Oct 16, 2018
da06438
License file-specific validations
agr Oct 16, 2018
ef4b152
[WIP] started license file storage implenentation.
agr Oct 16, 2018
667fea1
WIP
agr Oct 16, 2018
770ec4f
Merged dev
agr Oct 16, 2018
bf5e029
WIP
agr Oct 17, 2018
e4d20d3
DeleteLicenseFileAsync implementation.
agr Oct 17, 2018
3e6aa05
More tests.
agr Oct 17, 2018
b2ef6e7
Reading embedded license file metadata from nuspec.
agr Oct 17, 2018
a6a0485
Merge remote-tracking branch 'origin/dev' into agr-license-ingest
agr Oct 18, 2018
b1cce4f
license file textness detection bug fix
agr Oct 18, 2018
283a322
Moved license expression check higher in the validation process
agr Oct 18, 2018
239b315
Fix for the license metadata error text.
agr Oct 18, 2018
b1e5d46
Latest nuget.client libraries
agr Oct 18, 2018
0ef1be4
DB migration
agr Oct 18, 2018
068fefa
Specified the default value for the new column explicitly.
agr Oct 18, 2018
af07bfa
Removed currently unused setting.
agr Oct 18, 2018
dfa634b
Removed files that sneaked in.
agr Oct 18, 2018
df4f8e5
Added length of the license check. Addressed some PR feedback.
agr Oct 19, 2018
27b5d31
PR feedback addressed.
agr Oct 19, 2018
d29b495
More feedback addressed.
agr Oct 19, 2018
c10bb1b
More specific method name.
agr Oct 20, 2018
69c3350
Moved the text checking methods into their own class, moved and added…
agr Oct 20, 2018
4adf17d
More tests
agr Oct 20, 2018
f27e4eb
Added license node validaitons.
agr Oct 22, 2018
3527b25
Revert "Removed currently unused setting."
agr Oct 22, 2018
4cfc36e
Updated to the latest client libraries (so now it rejects UNLICENSED …
agr Oct 23, 2018
a05d31b
Not throwing exception in PackageService when license expression is e…
agr Oct 24, 2018
b5c2e33
Saving license expression in DB.
agr Oct 24, 2018
603de15
License expression save test
agr Oct 24, 2018
057b174
Deleting the license, when package is deleted.
agr Oct 24, 2018
2f52b09
Minor PR issues fixed
agr Oct 25, 2018
4c3a8db
Handling and testing of a zip files that report entry length incorrec…
agr Oct 26, 2018
4466876
test fix
agr Oct 29, 2018
e221264
Dropped migrations
agr Oct 31, 2018
d5118a7
Dropped the changes to entities preparing for the dev merge.
agr Oct 31, 2018
70eccfa
Merged with origin/dev
agr Oct 31, 2018
e2dbf14
More tests.
agr Nov 1, 2018
c7c588b
Nuspec generation bugfix
agr Nov 1, 2018
25feb93
Test updates/fixes.
agr Nov 1, 2018
da7829a
Re-added license mirations
agr Nov 1, 2018
5a0cb86
Disabling licene file uploads.
agr Nov 1, 2018
48978f1
Disabled license file related tests.
agr Nov 1, 2018
f359008
test fixes
agr Nov 2, 2018
ced1cde
Async stream I/O for license validation.
agr Nov 2, 2018
c89a90a
Removed the licenseUrl warnings until we have proper implementation.
agr Nov 6, 2018
8adb146
PR feedback
agr Nov 6, 2018
8014020
More PR feedback.
agr Nov 6, 2018
3274074
Proper entities reference.
agr Nov 7, 2018
7d9ddda
Friendlier license file size limit representation.
agr Nov 7, 2018
c193787
Comment on max license file size.
agr Nov 7, 2018
2d44295
Checking OSI/FSF licenses. Tracking some license validation failures.
agr Nov 8, 2018
52cd0bc
string and test fixes
agr Nov 8, 2018
834e684
Affected test fixes.
agr Nov 8, 2018
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions src/NuGetGallery.Core/CoreConstants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ public static class CoreConstants

public const string PackageFileSavePathTemplate = "{0}.{1}{2}";
public const string PackageFileBackupSavePathTemplate = "{0}/{1}/{2}.{3}";
public const string PackageContentFileSavePathTemplate = "{0}/{1}";

public const string NuGetPackageFileExtension = ".nupkg";
public const string CertificateFileExtension = ".cer";
Expand All @@ -20,6 +21,7 @@ public static class CoreConstants
public const string PackageContentType = "binary/octet-stream";
public const string OctetStreamContentType = "application/octet-stream";
public const string TextContentType = "text/plain";
public const string MarkdownContentType = "text/markdown"; // rfc7763
public const string CertificateContentType = "application/pkix-cert";
public const string JsonContentType = "application/json";

Expand All @@ -31,6 +33,7 @@ public static class CoreConstants
public const string PackageBackupsFolderName = "package-backups";
public const string PackageReadMesFolderName = "readmes";
public const string PackagesFolderName = "packages";
public const string PackagesContentFolderName = "packages-content";
public const string UploadsFolderName = "uploads";
public const string ValidationFolderName = "validation";
public const string RevalidationFolderName = "revalidation";
Expand All @@ -41,5 +44,7 @@ public static class CoreConstants
public const string SymbolPackageBackupsFolderName = "symbol-package-backups";

public const string UploadTracingKeyHeaderName = "upload-id";

public const string LicenseFileName = "license";
}
}
26 changes: 26 additions & 0 deletions src/NuGetGallery.Core/EmbeddedLicenseFileType.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

namespace NuGetGallery
{
/// <summary>
/// Specifies the type of the license file used in the package
/// </summary>
public enum EmbeddedLicenseFileType
{
/// <summary>
/// Indicates that package has no license file embedded.
/// </summary>
Absent = 0,

/// <summary>
/// Indicates that embedded license file is plain text.
/// </summary>
PlainText = 1,

/// <summary>
/// Indicates that embedded license file is markdown.
/// </summary>
Markdown = 2,
}
}
2 changes: 2 additions & 0 deletions src/NuGetGallery.Core/Entities/Package.cs
Original file line number Diff line number Diff line change
Expand Up @@ -245,5 +245,7 @@ public bool HasReadMe {
public virtual ICollection<SymbolPackage> SymbolPackages { get; set; }

public string Id => PackageRegistration.Id;

public EmbeddedLicenseFileType EmbeddedLicenseType { get; set; }
}
}
3 changes: 2 additions & 1 deletion src/NuGetGallery.Core/NuGetGallery.Core.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@
<Compile Include="Diagnostics\IDiagnosticsSource.cs" />
<Compile Include="Diagnostics\NullDiagnosticsSource.cs" />
<Compile Include="DisposableAction.cs" />
<Compile Include="EmbeddedLicenseFileType.cs" />
<Compile Include="Entities\Certificate.cs" />
<Compile Include="Entities\Credential.cs" />
<Compile Include="Entities\CuratedFeed.cs" />
Expand Down Expand Up @@ -294,7 +295,7 @@
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="NuGet.Packaging">
<Version>4.8.0-preview4.5287</Version>
<Version>5.0.0-preview1.5626</Version>
</PackageReference>
<PackageReference Include="NuGet.Services.Validation">
<Version>2.29.0</Version>
Expand Down
14 changes: 12 additions & 2 deletions src/NuGetGallery.Core/Packaging/PackageMetadata.cs
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,8 @@ public PackageMetadata(
IEnumerable<FrameworkSpecificGroup> frameworkGroups,
IEnumerable<NuGet.Packaging.Core.PackageType> packageTypes,
NuGetVersion minClientVersion,
RepositoryMetadata repositoryMetadata)
RepositoryMetadata repositoryMetadata,
LicenseMetadata licenseMetadata = null)
{
_metadata = new Dictionary<string, string>(metadata, StringComparer.OrdinalIgnoreCase);
_dependencyGroups = dependencyGroups.ToList().AsReadOnly();
Expand All @@ -67,6 +68,8 @@ public PackageMetadata(
RepositoryUrl = repoUrl;
RepositoryType = repositoryMetadata.Type;
}

LicenseMetadata = licenseMetadata;
}

private void SetPropertiesFromMetadata()
Expand Down Expand Up @@ -123,6 +126,12 @@ private void SetPropertiesFromMetadata()
public string Language { get; private set; }
public NuGetVersion MinClientVersion { get; set; }

/// <summary>
/// Contains license metadata taken from the 'license' node of the nuspec file.
/// Null if no 'license' node present.
/// </summary>
public LicenseMetadata LicenseMetadata { get; }

public string GetValueFromMetadata(string key)
{
return GetValue(key, (string)null);
Expand Down Expand Up @@ -244,7 +253,8 @@ public static PackageMetadata FromNuspecReader(NuspecReader nuspecReader, bool s
nuspecReader.GetFrameworkReferenceGroups(),
nuspecReader.GetPackageTypes(),
nuspecReader.GetMinClientVersion(),
nuspecReader.GetRepositoryMetadata());
nuspecReader.GetRepositoryMetadata(),
nuspecReader.GetLicenseMetadata());
}

private class StrictNuspecReader : NuspecReader
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -293,8 +293,19 @@ private static string Log(AccessCondition accessCondition)
return "(none)";
}

public async Task SaveFileAsync(string folderName, string fileName, Stream file, bool overwrite = true)
public Task SaveFileAsync(string folderName, string fileName, Stream file, bool overwrite = true)
{
var contentType = GetContentType(folderName);
return SaveFileAsync(folderName, fileName, contentType, file, overwrite);
}

public async Task SaveFileAsync(string folderName, string fileName, string contentType, Stream file, bool overwrite = true)
{
if (contentType == null)
{
throw new ArgumentNullException(nameof(contentType));
}

ICloudBlobContainer container = await GetContainerAsync(folderName);
var blob = container.GetBlobReference(fileName);

Expand All @@ -313,7 +324,7 @@ public async Task SaveFileAsync(string folderName, string fileName, Stream file,
ex);
}

blob.Properties.ContentType = GetContentType(folderName);
blob.Properties.ContentType = contentType;
blob.Properties.CacheControl = GetCacheControl(folderName);
await blob.SetPropertiesAsync();
}
Expand Down Expand Up @@ -554,6 +565,9 @@ private static string GetContentType(string folderName)
case CoreConstants.UserCertificatesFolderName:
return CoreConstants.CertificateContentType;

case CoreConstants.PackagesContentFolderName:
agr marked this conversation as resolved.
Show resolved Hide resolved
return CoreConstants.OctetStreamContentType;

default:
throw new InvalidOperationException(
String.Format(CultureInfo.CurrentCulture, "The folder name {0} is not supported.", folderName));
Expand All @@ -566,6 +580,7 @@ private static string GetCacheControl(string folderName)
{
case CoreConstants.PackagesFolderName:
case CoreConstants.SymbolPackagesFolderName:
case CoreConstants.PackagesContentFolderName:
return CoreConstants.DefaultCacheControl;

case CoreConstants.PackageBackupsFolderName:
Expand Down
72 changes: 72 additions & 0 deletions src/NuGetGallery.Core/Services/CorePackageFileService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ namespace NuGetGallery
{
public class CorePackageFileService : ICorePackageFileService
agr marked this conversation as resolved.
Show resolved Hide resolved
{
private const string LicenseFileName = "license";

private readonly ICoreFileStorageService _fileStorageService;
private readonly IFileMetadataService _metadata;

Expand Down Expand Up @@ -198,6 +200,76 @@ public async Task StorePackageFileInBackupLocationAsync(Package package, Stream
}
}

public Task SaveLicenseFileAsync(Package package, Stream licenseFile)
{
if (package == null)
{
throw new ArgumentNullException(nameof(package));
}

if (licenseFile == null)
{
throw new ArgumentNullException(nameof(licenseFile));
}

if (package.EmbeddedLicenseType == EmbeddedLicenseFileType.Absent)
{
throw new ArgumentException("Package must have embedded license", nameof(package));
agr marked this conversation as resolved.
Show resolved Hide resolved
}

var fileName = BuildLicenseFileName(package);

// Gallery will generally ignore the content type on license files and will use value from the DB,
agr marked this conversation as resolved.
Show resolved Hide resolved
// but we'll be nice and try to specify correct content type for them.
var contentType = package.EmbeddedLicenseType == EmbeddedLicenseFileType.Markdown
? CoreConstants.MarkdownContentType
: CoreConstants.TextContentType;

return _fileStorageService.SaveFileAsync(_metadata.PackageContentFolderName, fileName, contentType, licenseFile, overwrite: false);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SaveFileAsync [](start = 39, length = 13)

As far as I can tell, SaveFileAsync doesn't reset the position of the stream. Should we reset the position of licenseFile?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure why would we. Method's purpose is to save what was passed to it, not seek the stream here and there.

Copy link
Contributor Author

@agr agr Oct 26, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And by the way: zip archive streams seem to be not seekable, so can't rewind.

}

public Task<Stream> DownloadLicenseFileAsync(Package package)
{
var fileName = BuildLicenseFileName(package);
return _fileStorageService.GetFileAsync(_metadata.PackageContentFolderName, fileName);
}

public Task DeleteLicenseFileAsync(string id, string version)
{
if (id == null)
{
throw new ArgumentNullException(nameof(id));
}

if (string.IsNullOrWhiteSpace(id))
{
throw new ArgumentException($"{nameof(id)} cannot be empty", nameof(id));
}

if (version == null)
{
throw new ArgumentNullException(nameof(version));
}

if (string.IsNullOrWhiteSpace(version))
{
throw new ArgumentException($"{nameof(version)} cannot be empty", nameof(version));
}

var normalizedVersion = NuGetVersionFormatter.Normalize(version);
var fileName = BuildLicenseFileName(id, normalizedVersion);

return _fileStorageService.DeleteFileAsync(_metadata.PackageContentFolderName, fileName);
}

private string LicensePathTemplate => $"{_metadata.PackageContentPathTemplate}/{LicenseFileName}";

private string BuildLicenseFileName(Package package)
=> BuildFileName(package, LicensePathTemplate, string.Empty);

private string BuildLicenseFileName(string id, string version)
=> BuildFileName(id, version, LicensePathTemplate, string.Empty);

private static string BuildBackupFileName(string id, string version, string hash, string extension, string fileBackupSavePathTemplate)
{
if (id == null)
Expand Down
15 changes: 15 additions & 0 deletions src/NuGetGallery.Core/Services/ICoreFileStorageService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,21 @@ Task<Uri> GetPriviledgedFileUriAsync(

Task SaveFileAsync(string folderName, string fileName, Stream file, bool overwrite = true);

/// <summary>
/// Saves the file. If storage supports setting the content type for the file,
/// it will be set to the specified value
/// </summary>
/// <param name="folderName">The folder that will contain the file.</param>
/// <param name="fileName">The name of the file.</param>
/// <param name="contentType">The content type to set for the saved file if storage supports it.</param>
/// <param name="file">The content that should be saved.</param>
/// <param name="overwrite">Indicates whether file should be overwritten if exists.</param>
/// <exception cref="FileAlreadyExistsException">
/// Thrown when <paramref name="overwrite"/> is false and file already exists
/// in destination.
/// </exception>
Task SaveFileAsync(string folderName, string fileName, string contentType, Stream file, bool overwrite = true);

/// <summary>
/// Saves the file. An exception should be thrown if the access condition is not met.
/// </summary>
Expand Down
18 changes: 18 additions & 0 deletions src/NuGetGallery.Core/Services/ICorePackageFileService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,21 @@ public interface ICorePackageFileService
/// </summary>
Task SavePackageFileAsync(Package package, Stream packageFile, bool overwrite);

/// <summary>
/// Saves the license file to the public container for package content.
/// </summary>
Task SaveLicenseFileAsync(Package package, Stream licenseFile);

/// <summary>
/// Downloads the package from the file storage and reads it into a stream.
/// </summary>
Task<Stream> DownloadPackageFileAsync(Package package);

/// <summary>
/// Downloads previously saved license file for a specified package.
/// </summary>
Task<Stream> DownloadLicenseFileAsync(Package package);

/// <summary>
/// Generates the URL for the specified package in the public container for available packages.
/// </summary>
Expand Down Expand Up @@ -87,6 +97,14 @@ public interface ICorePackageFileService
/// <param name="version">The package version. This value is case-insensitive and need not be normalized.</param>
Task DeletePackageFileAsync(string id, string version);

/// <summary>
/// Deletes the license file for the package from the publicly available storage for the package content.
/// </summary>
/// <param name="id">The package ID. This value is case-insensitive.</param>
/// <param name="version">The package version. This value is case-insensitive and need not be normalized.</param>
/// <returns></returns>
Task DeleteLicenseFileAsync(string id, string version);

/// <summary>
/// Copies the contents of the package represented by the stream into the file storage backup location.
/// </summary>
Expand Down
10 changes: 10 additions & 0 deletions src/NuGetGallery.Core/Services/IFileMetadataService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,16 @@ public interface IFileMetadataService
/// </summary>
string FileFolderName { get; }

/// <summary>
/// The name of the public folder where bits of package content can be extracted to.
/// </summary>
string PackageContentFolderName { get; }

/// <summary>
/// The template for the path to save user content. File name will be appended to it.
/// </summary>
string PackageContentPathTemplate { get; }

/// <summary>
/// The save file path template. For example <see cref="CoreConstants.PackageFileSavePathTemplate"/>
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ public class PackageFileMetadataService : IFileMetadataService
{
public string FileFolderName => CoreConstants.PackagesFolderName;

public string PackageContentFolderName => CoreConstants.PackagesContentFolderName;

public string PackageContentPathTemplate => CoreConstants.PackageContentFileSavePathTemplate;

public string FileSavePathTemplate => CoreConstants.PackageFileSavePathTemplate;

public string FileExtension => CoreConstants.NuGetPackageFileExtension;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;

namespace NuGetGallery
{
public class SymbolPackageFileMetadataService : IFileMetadataService
{
public string FileFolderName => CoreConstants.SymbolPackagesFolderName;

public string PackageContentFolderName => throw new NotImplementedException();
public string PackageContentPathTemplate => throw new NotImplementedException();

public string FileSavePathTemplate => CoreConstants.PackageFileSavePathTemplate;

public string FileExtension => CoreConstants.NuGetSymbolPackageFileExtension;
Expand Down
6 changes: 6 additions & 0 deletions src/NuGetGallery/Configuration/AppConfiguration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -355,5 +355,11 @@ public string ExternalBrandingMessage

[DefaultValue(false)]
public bool RejectPackagesWithLicense { get; set; }

[DefaultValue(false)]
public bool BlockLegacyLicenseUrl { get; set; }

[DefaultValue(true)]
public bool AllowLicenselessPackages { get; set; }
}
}
11 changes: 11 additions & 0 deletions src/NuGetGallery/Configuration/IAppConfiguration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -365,5 +365,16 @@ public interface IAppConfiguration : IMessageServiceConfiguration
/// Flag that indicates whether packages with `license` node in them should be rejected.
/// </summary>
bool RejectPackagesWithLicense { get; set; }

/// <summary>
/// Indicates whether packages that specify the licene the "old" way (with a "licenseUrl" node only) should be rejected.
agr marked this conversation as resolved.
Show resolved Hide resolved
/// </summary>
bool BlockLegacyLicenseUrl { get; set; }

/// <summary>
/// Indicates whether packages that don't specify any license information (no license URL, no license expression,
/// no embedded license) are allowed into Gallery.
/// </summary>
bool AllowLicenselessPackages { get; set; }
}
}
Loading