- Ensure every artifact info has a matching FilesToSign or FilesToExcludeFromSigning item
- Add a NuGet package verifier rule to ensure all files in a nupkg are accouned for in the sign request
- Add test project for NuGetPackageVerifier
This commit is contained in:
Nate McMaster 2017-12-28 11:13:26 -08:00 коммит произвёл GitHub
Родитель 3e0fb6dc52
Коммит f85a36ecd0
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
22 изменённых файлов: 510 добавлений и 50 удалений

Просмотреть файл

@ -92,6 +92,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ApiCheckForwardDestination"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Internal.AspNetCore.SiteExtension.Sdk", "src\Internal.AspNetCore.SiteExtension.Sdk\Internal.AspNetCore.SiteExtension.Sdk.csproj", "{418F99A5-5EC4-4895-B8EB-7F8BBA241DB2}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NuGetPackageVerifier.Tests", "test\NuGetPackageVerifier.Tests\NuGetPackageVerifier.Tests.csproj", "{439CC7A3-F6E6-46B8-B6A0-05E22E558FC2}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -166,6 +168,10 @@ Global
{418F99A5-5EC4-4895-B8EB-7F8BBA241DB2}.Debug|Any CPU.Build.0 = Debug|Any CPU
{418F99A5-5EC4-4895-B8EB-7F8BBA241DB2}.Release|Any CPU.ActiveCfg = Release|Any CPU
{418F99A5-5EC4-4895-B8EB-7F8BBA241DB2}.Release|Any CPU.Build.0 = Release|Any CPU
{439CC7A3-F6E6-46B8-B6A0-05E22E558FC2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{439CC7A3-F6E6-46B8-B6A0-05E22E558FC2}.Debug|Any CPU.Build.0 = Debug|Any CPU
{439CC7A3-F6E6-46B8-B6A0-05E22E558FC2}.Release|Any CPU.ActiveCfg = Release|Any CPU
{439CC7A3-F6E6-46B8-B6A0-05E22E558FC2}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -189,6 +195,7 @@ Global
{85C30B96-BCEA-4C9C-99AA-6DAEB448EEF3} = {BD3545FB-5520-43DF-B4F9-83BEA3A38ECA}
{605F0478-A9D2-4A8A-BB38-9D5DC132FBB5} = {60A938B2-D95A-403C-AA7A-3683AD64DFA0}
{418F99A5-5EC4-4895-B8EB-7F8BBA241DB2} = {A4F4353B-C3D2-40B0-909A-5B48A748EA76}
{439CC7A3-F6E6-46B8-B6A0-05E22E558FC2} = {60A938B2-D95A-403C-AA7A-3683AD64DFA0}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {1B8809C8-A6C3-4761-BC91-B12841F49AE1}

Просмотреть файл

@ -54,5 +54,4 @@ extending the *DependsOn property
<!-- For external analysis of inputs/outputs. -->
<Target Name="GetArtifactInfo" DependsOnTargets="$(GetArtifactInfoDependsOn)" Returns="@(ArtifactInfo)" />
</Project>

Просмотреть файл

@ -27,7 +27,8 @@ that matches "$(RepositoryRoot)/shared/*.Sources".
Condition="@(SharedSourceDirectories->Count()) != 0"
BuildInParallel="true">
<Output TaskParameter="TargetOutputs" ItemName="ArtifactInfo" />
<Output TaskParameter="TargetOutputs" ItemName="ExcludeFromSigning" Condition="'$(SignSourcesPackages)' != 'true'" />
<Output TaskParameter="TargetOutputs" ItemName="FilesToExcludeFromSigning" Condition="'$(SignSourcesPackages)' != 'true'" />
<Output TaskParameter="TargetOutputs" ItemName="FilesToSign" Condition="'$(SignSourcesPackages)' == 'true'" />
</MSBuild>
</Target>

Просмотреть файл

@ -69,6 +69,11 @@
<Version>$(NormalizedPackageVersion)</Version>
<TargetFramework>$(TargetFramework)</TargetFramework>
<RepositoryRoot>$(RepositoryRoot)</RepositoryRoot>
<RepositoryUrl>$(RepositoryUrl)</RepositoryUrl>
<Category>$(PackageArtifactCategory)</Category>
<IsContainer>true</IsContainer>
<Certificate>$(PackageSigningCertName)</Certificate>
<ShouldBeSigned Condition=" '$(PackageSigningCertName)' != '' ">true</ShouldBeSigned>
</ArtifactInfo>
</ItemGroup>
</Target>

Просмотреть файл

@ -21,6 +21,7 @@
<TargetFrameworks>$([MSBuild]::Escape($(TargetFrameworks)))</TargetFrameworks>
<PackageType>$(PackageType)</PackageType>
<RepositoryRoot>$(RepositoryRoot)</RepositoryRoot>
<RepositoryUrl>$(RepositoryUrl)</RepositoryUrl>
<Category>$(PackageArtifactCategory)</Category>
<Certificate>$(PackageSigningCertName)</Certificate>
<ShouldBeSigned Condition="'$(PackageSigningCertName)' != '' OR @(SignedPackageFile->Count()) != 0 ">true</ShouldBeSigned>
@ -36,6 +37,7 @@
<SourceIncluded>$(IncludeSource)</SourceIncluded>
<PackageType>$(PackageType)</PackageType>
<RepositoryRoot>$(RepositoryRoot)</RepositoryRoot>
<RepositoryUrl>$(RepositoryUrl)</RepositoryUrl>
<Category>$(PackageArtifactCategory)</Category>
<Certificate>$(PackageSigningCertName)</Certificate>
<ShouldBeSigned Condition="'$(PackageSigningCertName)' != '' OR @(SignedPackageFile->Count()) != 0 ">true</ShouldBeSigned>
@ -47,19 +49,30 @@
<Container>$(FullPackageOutputPath)</Container>
</ArtifactInfo>
<ArtifactInfo Include="@(ExcludePackageFileFromSigning)">
<ShouldBeSigned>false</ShouldBeSigned>
<Container>$(FullPackageOutputPath)</Container>
</ArtifactInfo>
<ArtifactInfo Include="@(SignedPackageFile)" Condition="'$(IncludeSymbols)' == 'true' AND '$(NuspecFile)' == ''">
<ShouldBeSigned>true</ShouldBeSigned>
<Container>$(SymbolsPackageOutputPath)</Container>
</ArtifactInfo>
<ArtifactInfo Include="@(ExcludePackageFileFromSigning)" Condition="'$(IncludeSymbols)' == 'true' AND '$(NuspecFile)' == ''">
<ShouldBeSigned>false</ShouldBeSigned>
<Container>$(SymbolsPackageOutputPath)</Container>
</ArtifactInfo>
</ItemGroup>
</Target>
<!--
####################################################################################
Target: GetSignedPackageFiles
Gets itesm for built assemblies that will be in the package.
Also supports projects that explicitly set SignedPackageFile.
Gets items for built assemblies that will be in the NuGet package.
Also supports projects that explicitly set items in the SignedPackageFile group.
Items:
[out] SignedPackageFile

Просмотреть файл

@ -155,8 +155,14 @@ Executes /t:Pack on all projects matching src/*/*.csproj.
</MSBuild>
<ItemGroup>
<!-- Output from this target may include items representing assemblies inside the nupkg. -->
<ArtifactInfo Include="@(_Temp)" Condition="'%(_Temp.Container)' == ''" />
<Sign Include="@(_Temp)" Condition="'%(_Temp.Container)' != ''" />
<!-- Nupkgs or assemblies in the nupkg that should be signed -->
<FilesToSign Include="@(_Temp)" Condition=" '%(_Temp.ShouldBeSigned)' == 'true' " />
<!-- Assemblies inside a nupkg that should not be signed -->
<FilesToExcludeFromSigning Include="@(_Temp)" Condition=" '%(_Temp.ShouldBeSigned)' == 'false' AND '%(_Temp.Container)' != ''" />
</ItemGroup>
</Target>

Просмотреть файл

@ -125,28 +125,31 @@ namespace KoreBuild.Tasks
}
}
foreach (var item in Exclusions)
if (Exclusions != null)
{
var normalizedPath = NormalizePath(BasePath, item.ItemSpec);
var containerPath = item.GetMetadata("Container");
if (!string.IsNullOrEmpty(containerPath))
foreach (var item in Exclusions)
{
if (!containers.TryGetValue(containerPath, out var container))
var normalizedPath = NormalizePath(BasePath, item.ItemSpec);
var containerPath = item.GetMetadata("Container");
if (!string.IsNullOrEmpty(containerPath))
{
Log.LogError($"Exclusion item '{item.ItemSpec}' specifies an unknown container '{containerPath}'.");
continue;
}
if (!containers.TryGetValue(containerPath, out var container))
{
Log.LogError($"Exclusion item '{item.ItemSpec}' specifies an unknown container '{containerPath}'.");
continue;
}
var packagePath = item.GetMetadata("PackagePath");
normalizedPath = string.IsNullOrEmpty(packagePath) ? normalizedPath : packagePath.Replace('\\', '/');
var file = new SignRequestItem.Exclusion(normalizedPath);
container.AddItem(file);
}
else
{
var file = new SignRequestItem.Exclusion(normalizedPath);
signRequestCollection.Add(file);
var packagePath = item.GetMetadata("PackagePath");
normalizedPath = string.IsNullOrEmpty(packagePath) ? normalizedPath : packagePath.Replace('\\', '/');
var file = new SignRequestItem.Exclusion(normalizedPath);
container.AddItem(file);
}
else
{
var file = new SignRequestItem.Exclusion(normalizedPath);
signRequestCollection.Add(file);
}
}
}

Просмотреть файл

@ -30,5 +30,7 @@ namespace KoreBuild.Tasks
// not used in code, but reserved for MSBuild targets
public const int ArtifactInfoMismatch = 5002;
public const int FilesToSignMismatchedWithArtifactInfo = 5003;
public const int FilesToSignMissingCertInfo = 5004;
}
}

Просмотреть файл

@ -148,35 +148,75 @@ and NodeJS.
</Target>
<!--
####################################################################################
Target: VerifySignRequestItems
Verifies all artifact items have a corresponding sign item.
####################################################################################
-->
<Target Name="VerifySignRequestItems"
DependsOnTargets="GetArtifactInfo"
Condition="'$(GenerateSignRequest)' == 'true' AND '$(SkipArtifactVerification)' != 'true'">
<ItemGroup>
<_ExpectedFileToSign Remove="@(_ExpectedFileToSign)" />
<_ExpectedFileToSign Include="@(ArtifactInfo)" />
<_ExpectedFileToSign Remove="@(FilesToSign);@(FilesToExcludeFromSigning);$(SignRequestOutputPath)" />
<_FilesToSignMissingConfig Remove="@(_FilesToSignMissingConfig)" />
<_FilesToSignMissingConfig Include="@(FilesToSign)" Condition=" '%(FilesToSign.Certificate)' == '' AND '%(FilesToSign.StrongName)' == '' AND '%(FilesToSign.IsContainer)' != 'true' " />
</ItemGroup>
<PropertyGroup>
<_SigningErrorMessage Condition=" @(_ExpectedFileToSign->Count()) != 0 ">
Could not determine signing information for all ArtifactInfo items.
Fix this error by adding these items to FilesToSign or FilesToExcludeFromSigning:
- @(_ExpectedFileToSign, '%0A - ')
</_SigningErrorMessage>
</PropertyGroup>
<Error Text="$(_SigningErrorMessage.Trim())"
Code="KRB5003"
Condition=" @(_ExpectedFileToSign->Count()) != 0 " />
<PropertyGroup>
<_SigningErrorMessage Condition=" @(_FilesToSignMissingConfig->Count()) != 0 ">
The following FilesToSign did not specify a Certificate or StrongName to use.
- @(_FilesToSignMissingConfig, '%0A - ')
</_SigningErrorMessage>
</PropertyGroup>
<Error Text="$(_SigningErrorMessage.Trim())"
Code="KRB5004"
Condition=" @(_FilesToSignMissingConfig->Count()) != 0 " />
</Target>
<!--
####################################################################################
Target: GenerateSignRequest
Generates a manifest that contains signin requests for files.
[in] (items) ArtifactInfo
[in] (items) Sign
[in] (prop) Artifacts
[in] (items) FilesToSign
[in] (items) FilesToExcludeFromSigning
[out] SignRequestOutputPath - the bom file
####################################################################################
-->
<ItemGroup Condition=" '$(GenerateSignRequest)' == 'true' ">
<ArtifactInfo Include="$(SignRequestOutputPath)">
<ArtifactType>XmlFile</ArtifactType>
<Category>noship</Category>
</ArtifactInfo>
</ItemGroup>
<Target Name="GenerateSignRequest"
DependsOnTargets="GetArtifactInfo"
DependsOnTargets="GetArtifactInfo;VerifySignRequestItems"
Condition=" '$(GenerateSignRequest)' == 'true' ">
<ItemGroup>
<ArtifactInfo Include="$(SignRequestOutputPath)">
<ArtifactType>XmlFile</ArtifactType>
<Category>noship</Category>
</ArtifactInfo>
<Sign Include="@(ArtifactInfo)" Condition=" '%(ArtifactInfo.ShouldBeSigned)' == 'true' " />
</ItemGroup>
<GenerateSignRequest
Requests="@(Sign)"
Exclusions="@(ExcludeFromSigning)"
Requests="@(FilesToSign)"
Exclusions="@(FilesToExcludeFromSigning)"
BasePath="$(ArtifactsDir)"
OutputPath="$(SignRequestOutputPath)" />
</Target>

Просмотреть файл

@ -1,4 +1,4 @@
// Copyright (c) .NET Foundation. All rights reserved.
// 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.Collections.Generic;
@ -28,6 +28,7 @@ namespace NuGetPackageVerifier.Rules
new PackageTypesRule(),
new PackageVersionMatchesAssemblyVersionRule(),
new BuildItemsRule(),
new SignRequestListsAllSignableFiles(),
};
}
}

Просмотреть файл

@ -0,0 +1,13 @@
// 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.Collections.Generic;
namespace NuGetPackageVerifier.Manifests
{
public class PackageSignRequest
{
public ISet<string> FilesExcludedFromSigning { get; set; }
public ISet<string> FilesToSign { get; set; }
}
}

Просмотреть файл

@ -0,0 +1,55 @@
// 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;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Xml.Linq;
namespace NuGetPackageVerifier.Manifests
{
public class SignRequestManifest
{
/// <summary>
/// Represents all signing requests in the sign request manifest that are for nupkg files.
/// </summary>
public IReadOnlyDictionary<string, PackageSignRequest> PackageSignRequests { get; private set; }
public static SignRequestManifest Parse(string filePath)
{
using (var reader = File.OpenText(filePath))
{
return Parse(reader, Path.GetDirectoryName(filePath));
}
}
public static SignRequestManifest Parse(TextReader reader, string manifestBasePath)
{
var doc = XDocument.Load(reader);
var requests = new Dictionary<string, PackageSignRequest>(StringComparer.OrdinalIgnoreCase);
var manifest = new SignRequestManifest { PackageSignRequests = requests };
var nupkgContainers = doc.Root
.Elements("Container")
.Where(c => "nupkg".Equals(c.Attribute("Type")?.Value, StringComparison.Ordinal));
foreach (var container in nupkgContainers)
{
var request = new PackageSignRequest
{
FilesToSign = container.Elements("File").Select(GetPath).ToHashSet(StringComparer.Ordinal),
FilesExcludedFromSigning = container.Elements("ExcludedFile").Select(GetPath).ToHashSet(StringComparer.Ordinal),
};
var path = new FileInfo(Path.Combine(manifestBasePath, GetPath(container))).FullName;
requests.Add(path, request);
}
return manifest;
}
private static string GetPath(XElement element) => element.Attribute("Path")?.Value;
}
}

Просмотреть файл

@ -6,6 +6,7 @@ using System.Collections.Generic;
using System.IO;
using NuGet.Packaging;
using NuGetPackageVerifier.Logging;
using NuGetPackageVerifier.Manifests;
namespace NuGetPackageVerifier
{
@ -14,6 +15,7 @@ namespace NuGetPackageVerifier
private PackageArchiveReader _reader;
public FileInfo PackageFileInfo { get; set; }
public PackageSignRequest SignRequest { get; set; }
public IPackageMetadata Metadata { get; set; }
public PackageVerifierOptions Options { get; set; }
public IPackageVerifierLogger Logger { get; set; }
@ -21,7 +23,7 @@ namespace NuGetPackageVerifier
public IDictionary<string, AssemblyAttributesData> AssemblyData { get; } = new Dictionary<string, AssemblyAttributesData>();
public void Dispose()
public virtual void Dispose()
{
_reader?.Dispose();
}

Просмотреть файл

@ -37,6 +37,15 @@ namespace NuGetPackageVerifier
);
}
public static PackageVerifierIssue SignRequestMissingPackageFile(string id, string filePath)
{
return new PackageVerifierIssue(
"FILE_MISSING_FROM_SIGN_REQUEST",
filePath,
string.Format("The sign request for package {0} does not specify what to do with signable file {1}", id, filePath),
PackageIssueLevel.Error);
}
public static PackageVerifierIssue PackageTypeMissing(string packageType)
{
return new PackageVerifierIssue(

Просмотреть файл

@ -10,6 +10,7 @@ using Microsoft.Extensions.CommandLineUtils;
using Newtonsoft.Json;
using NuGet.Packaging;
using NuGetPackageVerifier.Logging;
using NuGetPackageVerifier.Manifests;
namespace NuGetPackageVerifier
{
@ -25,6 +26,7 @@ namespace NuGetPackageVerifier
var verbose = application.Option("--verbose", "Verbose output and assistance", CommandOptionType.NoValue);
var ruleFile = application.Option("--rule-file", "Path to NPV.json", CommandOptionType.SingleValue);
var excludedRules = application.Option("--excluded-rule", "Rules to exclude. Calculcated after composite rules are evaluated.", CommandOptionType.MultipleValue);
var signRequest = application.Option("--sign-request", "Sign request manifest file.", CommandOptionType.SingleValue);
var packageDirectory = application.Argument("Package directory", "Package directory to scan for nupkgs");
application.OnExecute(() =>
@ -37,7 +39,7 @@ namespace NuGetPackageVerifier
return ReturnBadArgs;
}
if (!ruleFile.HasValue())
if (!ruleFile.HasValue())
{
application.Error.WriteAsync($"Missing required option {ruleFile.Template}.");
application.ShowHelp();
@ -58,7 +60,7 @@ namespace NuGetPackageVerifier
// TODO: Show extraneous packages, exclusions, etc.
var ignoreAssistanceMode = verbose.HasValue() ? IgnoreAssistanceMode.ShowAll : IgnoreAssistanceMode.ShowNew;
var ruleFileContent = File.ReadAllText(ruleFile.Value());
var packageSets = JsonConvert.DeserializeObject<IDictionary<string, PackageSet>>(
ruleFileContent,
@ -67,12 +69,17 @@ namespace NuGetPackageVerifier
MissingMemberHandling = MissingMemberHandling.Error
});
var signRequestManifest = signRequest.HasValue()
? SignRequestManifest.Parse(signRequest.Value())
: default;
logger.LogNormal("Read {0} package set(s) from {1}", packageSets.Count, ruleFile.Value());
var nupkgs = new DirectoryInfo(packageDirectory.Value).EnumerateFiles("*.nupkg", SearchOption.TopDirectoryOnly)
.Where(p => !p.Name.EndsWith(".symbols.nupkg"))
.ToArray();
logger.LogNormal("Found {0} packages in {1}", nupkgs.Length, packageDirectory.Value);
var exitCode = Execute(packageSets, nupkgs, excludedRules.Values, logger, ignoreAssistanceMode);
var exitCode = Execute(packageSets, nupkgs, signRequestManifest, excludedRules.Values, logger, ignoreAssistanceMode);
totalTimeStopWatch.Stop();
logger.LogNormal("Total took {0}ms", totalTimeStopWatch.ElapsedMilliseconds);
@ -85,6 +92,7 @@ namespace NuGetPackageVerifier
private static int Execute(
IDictionary<string, PackageSet> packageSets,
IEnumerable<FileInfo> nupkgs,
SignRequestManifest signRequestManifest,
List<string> excludedRuleNames,
IPackageVerifierLogger logger,
IgnoreAssistanceMode ignoreAssistanceMode)
@ -94,7 +102,7 @@ namespace NuGetPackageVerifier
typeof(IPackageVerifierRule).IsAssignableFrom(t) && !t.IsAbstract)
.ToDictionary(
t => t.Name,
t =>
t =>
{
var rule = (IPackageVerifierRule)Activator.CreateInstance(t);
if (rule is CompositeRule compositeRule)
@ -176,13 +184,17 @@ namespace NuGetPackageVerifier
var package = packagePair.Key;
logger.LogInfo("Analyzing {0} ({1})", package.Id, package.Version);
PackageSignRequest signRequest = null;
signRequestManifest?.PackageSignRequests.TryGetValue(packagePair.Value.FullName, out signRequest);
List<PackageVerifierIssue> issues;
using (var context = new PackageAnalysisContext
{
PackageFileInfo = packagePair.Value,
Metadata = package,
Logger = logger,
Options = packageInfo.Value
Options = packageInfo.Value,
SignRequest = signRequest,
})
{
issues = analyzer.AnalyzePackage(context).ToList();
@ -223,9 +235,9 @@ namespace NuGetPackageVerifier
// For unlisted packages we run the rules from 'Default' package set if present
// or we run all rules (because we have no idea what exactly to run)
var analyzer = new PackageAnalyzer();
var unlistedPackageRules = defaultRuleSet ??
var unlistedPackageRules = defaultRuleSet ??
allRules.Values.SelectMany(f => f).Where(r => !excludedRuleNames.Contains(r.GetType().Name));
foreach (var ruleInstance in unlistedPackageRules)
{
analyzer.Rules.Add(ruleInstance);
@ -237,6 +249,9 @@ namespace NuGetPackageVerifier
{
logger.LogInfo("Analyzing {0} ({1})", unlistedPackage.Id, unlistedPackage.Version);
PackageSignRequest signRequest = null;
signRequestManifest?.PackageSignRequests.TryGetValue(packages[unlistedPackage].FullName, out signRequest);
List<PackageVerifierIssue> issues;
PackageVerifierOptions packageOptions = null;
defaultPackageSet?.Packages?.TryGetValue(unlistedPackage.Id, out packageOptions);
@ -246,6 +261,7 @@ namespace NuGetPackageVerifier
PackageFileInfo = packages[unlistedPackage],
Metadata = unlistedPackage,
Logger = logger,
SignRequest = signRequest,
Options = packageOptions,
})
{
@ -302,7 +318,7 @@ namespace NuGetPackageVerifier
"SUMMARY: {0} error(s) and {1} warning(s) found",
totalErrors, totalWarnings);
return (totalErrors + totalWarnings > 0) ? ReturnErrorsOrWarnings : ReturnOk;
}

Просмотреть файл

@ -0,0 +1,47 @@
// 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;
using System.Collections.Generic;
using System.IO;
using NuGetPackageVerifier.Logging;
namespace NuGetPackageVerifier.Rules
{
public class SignRequestListsAllSignableFiles : IPackageVerifierRule
{
private static readonly HashSet<string> SignableExtensions = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
{
".dll",
".exe",
".ps1",
".psd1",
".psm1",
".psc1",
".ps1xml",
};
public IEnumerable<PackageVerifierIssue> Validate(PackageAnalysisContext context)
{
if (context.SignRequest == null)
{
context.Logger.Log(LogLevel.Info, "Skipping signing rule request verification for " + context.PackageFileInfo.FullName);
yield break;
}
foreach (var file in context.PackageReader.GetFiles())
{
var ext = Path.GetExtension(file);
if (!SignableExtensions.Contains(ext))
{
continue;
}
if (!context.SignRequest.FilesToSign.Contains(file) && !context.SignRequest.FilesExcludedFromSigning.Contains(file))
{
yield return PackageIssueFactory.SignRequestMissingPackageFile(context.Metadata.Id, file);
}
}
}
}
}

Просмотреть файл

@ -25,12 +25,18 @@ repository root.
<Packages Include="$(BuildDir)*.nupkg" />
</ItemGroup>
<PropertyGroup>
<_VerifierSignRequestPath />
<_VerifierSignRequestPath Condition=" '$(GenerateSignRequest)' == 'true' ">$(SignRequestOutputPath)</_VerifierSignRequestPath>
</PropertyGroup>
<Warning Text="No nupkg found in '$(BuildDir)'." Condition="$(Packages -> Count()) == 0" />
<Warning Text="Skipping nuget package verification because artifacts directory could not be found"
Condition="!Exists('$(BuildDir)')" />
<VerifyPackages ArtifactDirectory="$(BuildDir)"
RuleFile="$(NuGetVerifierRuleFile)"
SignRequestManifest="$(_VerifierSignRequestPath)"
Condition="Exists('$(BuildDir)')" />
</Target>

Просмотреть файл

@ -1,4 +1,4 @@
// Copyright (c) .NET Foundation. All rights reserved.
// 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.Collections.Generic;
@ -27,6 +27,8 @@ namespace NuGetPackagerVerifier
public string[] ExcludedRules { get; set; }
public string SignRequestManifest { get; set; }
public override bool Execute()
{
if (string.IsNullOrEmpty(RuleFile) || !File.Exists(RuleFile))
@ -57,6 +59,18 @@ namespace NuGetPackagerVerifier
ArtifactDirectory,
};
if (!string.IsNullOrEmpty(SignRequestManifest))
{
if (!File.Exists(SignRequestManifest))
{
Log.LogError($"SignRequestManifest file {SignRequestManifest} does not exist.");
return false;
}
arguments.Add("--sign-request");
arguments.Add(SignRequestManifest);
}
foreach (var rule in ExcludedRules ?? Enumerable.Empty<string>())
{
arguments.Add("--excluded-rule");
@ -69,7 +83,8 @@ namespace NuGetPackagerVerifier
Arguments = ArgumentEscaper.EscapeAndConcatenate(arguments),
};
Log.LogMessage($"Executing '{psi.FileName} {psi.Arguments}'");
Log.LogCommandLine($"Executing '{psi.FileName} {psi.Arguments}'");
var process = Process.Start(psi);
process.WaitForExit();
return process.ExitCode == 0;

Просмотреть файл

@ -0,0 +1,17 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp2.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Moq" Version="$(MoqPackageVersion)" />
<PackageReference Include="xunit" Version="$(XunitPackageVersion)" />
<PackageReference Include="xunit.runner.visualstudio" Version="$(XunitRunnerVisualStudioPackageVersion)" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\modules\NuGetPackageVerifier\console\NuGetPackageVerifier.Console.csproj" />
</ItemGroup>
</Project>

Просмотреть файл

@ -0,0 +1,93 @@
// 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;
using NuGetPackageVerifier.Rules;
using NuGetPackageVerifier.Tests.Utilities;
using Xunit;
using Xunit.Abstractions;
namespace NuGetPackageVerifier.Tests
{
public class SignRequestListsAllSignableFilesRuleTests
{
private readonly ITestOutputHelper _output;
public SignRequestListsAllSignableFilesRuleTests(ITestOutputHelper output)
{
_output = output;
}
[Fact]
public void ItFailsWhenPackageContainsUnlistedFiles()
{
var signRequest = @"
<SignRequest>
<Container Path=""TestPackage.1.0.0.nupkg"" Type=""nupkg"">
</Container>
</SignRequest>";
var context = TestHelper.CreateAnalysisContext(_output,
new[] { "lib/netstandard2.0/Test.dll", "tools/MyScript.psd1" },
signRequest: signRequest);
var rule = new SignRequestListsAllSignableFiles();
var errors = rule.Validate(context);
Assert.NotEmpty(errors);
Assert.Contains(errors, e =>
e.Instance.Equals("lib/netstandard2.0/Test.dll", StringComparison.Ordinal)
&& e.IssueId.Equals("FILE_MISSING_FROM_SIGN_REQUEST", StringComparison.Ordinal));
Assert.Contains(errors, e =>
e.Instance.Equals("tools/MyScript.psd1", StringComparison.Ordinal)
&& e.IssueId.Equals("FILE_MISSING_FROM_SIGN_REQUEST", StringComparison.Ordinal));
}
[Fact]
public void DoesNotFailWhenSignRequestIncludesAllFiles()
{
var signRequest = @"
<SignRequest>
<Container Path=""TestPackage.1.0.0.nupkg"" Type=""nupkg"">
<File Path=""lib/netstandard2.0/Test.dll"" />
<File Path=""tools/MyScript.psd1"" />
</Container>
</SignRequest>";
var context = TestHelper.CreateAnalysisContext(_output,
new[] { "lib/netstandard2.0/Test.dll", "tools/MyScript.psd1" },
signRequest: signRequest);
var rule = new SignRequestListsAllSignableFiles();
var errors = rule.Validate(context);
Assert.Empty(errors);
}
[Fact]
public void DoesNotFailWhenSignRequestListsAllFiles()
{
var signRequest = @"
<SignRequest>
<Container Path=""TestPackage.1.0.0.nupkg"" Type=""nupkg"">
<ExcludedFile Path=""lib/netstandard2.0/Test.dll"" />
<ExcludedFile Path=""tools/MyScript.psd1"" />
</Container>
</SignRequest>";
var context = TestHelper.CreateAnalysisContext(_output,
new[] { "lib/netstandard2.0/Test.dll", "tools/MyScript.psd1" },
signRequest: signRequest);
var rule = new SignRequestListsAllSignableFiles();
var errors = rule.Validate(context);
Assert.Empty(errors);
}
}
}

Просмотреть файл

@ -0,0 +1,23 @@
// 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 NuGetPackageVerifier.Logging;
using Xunit.Abstractions;
namespace NuGetPackageVerifier.Tests.Utilities
{
internal class TestLogger : IPackageVerifierLogger
{
private ITestOutputHelper _output;
public TestLogger(ITestOutputHelper output)
{
_output = output;
}
public void Log(LogLevel logLevel, string message)
{
_output.WriteLine($"{logLevel}: {message}");
}
}
}

Просмотреть файл

@ -0,0 +1,87 @@
// 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;
using System.IO;
using NuGet.Packaging;
using NuGet.Versioning;
using NuGetPackageVerifier.Manifests;
using Xunit.Abstractions;
namespace NuGetPackageVerifier.Tests.Utilities
{
public class TestHelper
{
public static PackageAnalysisContext CreateAnalysisContext(ITestOutputHelper output, string[] emptyFiles, string version = "1.0.0", string signRequest = null)
{
const string packageId = "TestPackage";
var basePath = Path.Combine(AppContext.BaseDirectory, Path.GetRandomFileName());
var nupkgFileName = $"{packageId}.{version}.nupkg";
var nupkgPath = Path.Combine(basePath, nupkgFileName);
Directory.CreateDirectory(basePath);
var builder = new PackageBuilder();
builder.Populate(new ManifestMetadata
{
Id = packageId,
Version = new NuGetVersion(version),
Authors = new[] { "Test" },
Description = "Test",
});
using (var nupkg = File.Create(nupkgPath))
{
foreach (var dest in emptyFiles)
{
var fileName = Path.GetFileName(dest);
File.WriteAllText(Path.Combine(basePath, fileName), "");
builder.AddFiles(basePath, fileName, dest);
}
builder.Save(nupkg);
}
PackageSignRequest packageSignRequest = null;
if (signRequest != null)
{
var reader = new StringReader(signRequest);
var signManifest = SignRequestManifest.Parse(reader, basePath);
packageSignRequest = signManifest.PackageSignRequests[nupkgPath];
}
var context = new TempPackageAnalysisContext(basePath)
{
Logger = new TestLogger(output),
PackageFileInfo = new FileInfo(nupkgPath),
SignRequest = packageSignRequest,
Metadata = builder,
};
return context;
}
private class TempPackageAnalysisContext : PackageAnalysisContext
{
private string _tempPath;
public TempPackageAnalysisContext(string tempPath)
{
this._tempPath = tempPath;
}
public override void Dispose()
{
base.Dispose();
if (Directory.Exists(_tempPath))
{
Directory.Delete(_tempPath, recursive: true);
}
}
}
}
}