Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
11 changes: 10 additions & 1 deletion doBuild.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -95,8 +95,15 @@ function DoBuild
'Azure.Core'
'Azure.Identity'
'Microsoft.Bcl.AsyncInterfaces'
'Microsoft.Bcl.Cryptography'
'Microsoft.Extensions.Configuration.Abstractions'
'Microsoft.Extensions.DependencyInjection.Abstractions'
'Microsoft.Extensions.Diagnostics.Abstractions'
'Microsoft.Extensions.FileProviders.Abstractions'
'Microsoft.Extensions.FileSystemGlobbing'
'Microsoft.Extensions.Hosting.Abstractions'
'Microsoft.Extensions.Logging.Abstractions'
'Microsoft.Extensions.Options'
'Microsoft.Extensions.Primitives'
'Microsoft.Identity.Client'
'Microsoft.Identity.Client.Extensions.Msal'
Expand All @@ -117,18 +124,20 @@ function DoBuild
'System.Collections.Immutable'
'System.ClientModel'
'System.Diagnostics.DiagnosticSource'
'System.Formats.Asn1'
'System.IO.FileSystem.AccessControl'
'System.IO.Pipelines'
'System.Memory.Data'
'System.Memory'
'System.Numerics.Vectors'
'System.Runtime.CompilerServices.Unsafe'
'System.Security.AccessControl'
'System.Security.Cryptography.Pkcs'
'System.Security.Cryptography.ProtectedData'
'System.Security.Principal.Windows'
'System.Text.Encodings.Web'
'System.Text.Json'
'System.Threading.Tasks.Extensions'
'System.ValueTuple'
)

$buildSuccess = $true
Expand Down
18 changes: 9 additions & 9 deletions src/code/Microsoft.PowerShell.PSResourceGet.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -14,19 +14,19 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="NuGet.Commands" Version="6.14.3" />
<PackageReference Include="NuGet.Common" Version="6.14.3" />
<PackageReference Include="NuGet.Configuration" Version="6.14.3" />
<PackageReference Include="NuGet.Packaging" Version="6.14.3" />
<PackageReference Include="NuGet.ProjectModel" Version="6.14.3" />
<PackageReference Include="NuGet.Protocol" Version="6.14.3" />
<PackageReference Include="NuGet.Commands" Version="7.9.0" />
<PackageReference Include="NuGet.Common" Version="7.9.0" />
<PackageReference Include="NuGet.Configuration" Version="7.9.0" />
<PackageReference Include="NuGet.Packaging" Version="7.9.0" />
<PackageReference Include="NuGet.ProjectModel" Version="7.9.0" />
<PackageReference Include="NuGet.Protocol" Version="7.9.0" />
<PackageReference Include="PowerShellStandard.Library" Version="7.0.0-preview.1" />
<PackageReference Include="System.Net.Http" Version="4.3.4" />
<PackageReference Include="System.Text.Json" Version="8.0.6" />
<PackageReference Include="System.Text.Json" Version="10.0.13" />
<PackageReference Include="Microsoft.CSharp" Version="4.7.0" />
<PackageReference Include="Azure.Identity" Version="1.17.2" />
<PackageReference Include="Azure.Identity" Version="1.21.0" />
<PackageReference Include="System.Buffers" Version="4.6.1" />
<PackageReference Include="System.Security.Cryptography.Pkcs" Version="6.0.5" />
<PackageReference Include="System.Security.Cryptography.Pkcs" Version="10.0.13" />
<Reference Include="System.Web" />
</ItemGroup>

Expand Down
60 changes: 55 additions & 5 deletions src/code/ModuleInitAndCleanup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ public class UnsafeAssemblyHandler : IModuleAssemblyInitializer, IModuleAssembly
{
private static readonly Assembly s_self;
private static readonly string s_dependencyFolder;
private static readonly HashSet<string> s_dependencies;
private static readonly Dictionary<string, AssemblyName> s_dependencies;
private static readonly AssemblyLoadContextProxy s_proxy;

private static readonly HashSet<string> NetFrameworkLoadFromPath = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
Expand All @@ -30,12 +30,13 @@ static UnsafeAssemblyHandler()
{
s_self = Assembly.GetExecutingAssembly();
s_dependencyFolder = Path.Combine(Path.GetDirectoryName(s_self.Location), "dependencies");
s_dependencies = new HashSet<string>(StringComparer.Ordinal);
s_dependencies = new Dictionary<string, AssemblyName>(StringComparer.OrdinalIgnoreCase);
s_proxy = AssemblyLoadContextProxy.CreateLoadContext("psresourceget-load-context");

foreach (string filePath in Directory.EnumerateFiles(s_dependencyFolder, "*.dll"))
{
s_dependencies.Add(AssemblyName.GetAssemblyName(filePath).FullName);
AssemblyName dependency = AssemblyName.GetAssemblyName(filePath);
s_dependencies[dependency.Name] = dependency;
}
}

Expand All @@ -58,8 +59,57 @@ private static bool IsAssemblyMatching(AssemblyName assemblyName, Assembly reque
// Sometimes the requesting assembly can be one of the NuGet.* dlls, in such case, we check if the
// requesting assembly is part of our pre-defined dependencies list.
return requestingAssembly is not null
? (requestingAssembly == s_self || (s_dependencies.Contains(requestingAssembly.FullName))) && s_dependencies.Contains(assemblyName.FullName)
: s_dependencies.Contains(assemblyName.FullName);
? (requestingAssembly == s_self || IsBundledDependency(requestingAssembly.GetName())) && IsCompatibleDependency(assemblyName)
: IsCompatibleDependency(assemblyName);
}

private static bool IsBundledDependency(AssemblyName assemblyName)
{
return s_dependencies.TryGetValue(assemblyName.Name, out AssemblyName dependency)
&& string.Equals(dependency.FullName, assemblyName.FullName, StringComparison.OrdinalIgnoreCase);
}

// Dependencies are often compiled against older versions of shared assemblies than the ones bundled
// with this module (e.g. Azure.Core references System.Text.Json 10.0.0.3 while 10.0.0.13 is bundled).
// Without binding redirects, accept any request for the same assembly whose version is not newer than the bundled one.
private static bool IsCompatibleDependency(AssemblyName requested)
{
if (!s_dependencies.TryGetValue(requested.Name, out AssemblyName bundled))
{
return false;
}

if (requested.Version is not null && requested.Version > bundled.Version)
{
return false;
}

if (!string.Equals(requested.CultureName ?? string.Empty, bundled.CultureName ?? string.Empty, StringComparison.OrdinalIgnoreCase))
{
return false;
}

byte[] requestedToken = requested.GetPublicKeyToken();
if (requestedToken is null || requestedToken.Length == 0)
{
return true;
}

byte[] bundledToken = bundled.GetPublicKeyToken();
if (bundledToken is null || bundledToken.Length != requestedToken.Length)
{
return false;
}

for (int i = 0; i < requestedToken.Length; i++)
{
if (requestedToken[i] != bundledToken[i])
{
return false;
}
}

return true;
}

private static Assembly HandleAssemblyResolve(object sender, ResolveEventArgs args)
Expand Down
6 changes: 5 additions & 1 deletion src/code/PublishHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ internal enum CallerCmdlet
private ResourceType resourceType = ResourceType.None;
string userAgentString = UserAgentInfo.UserAgentString();
private bool _isNupkgPathSpecified = false;
private readonly bool _allowInsecureConnection;
private Hashtable dependencies;
private Hashtable parsedMetadata;
private PSCredential Credential;
Expand Down Expand Up @@ -87,7 +88,8 @@ internal PublishHelper(PSCmdlet cmdlet,
string destinationPath,
bool skipModuleManifestValidate,
CancellationToken cancellationToken,
bool isNupkgPathSpecified)
bool isNupkgPathSpecified,
bool allowInsecureConnection)
{
_callerCmdlet = CallerCmdlet.PublishPSResource;
_cmdOperation = "Publish";
Expand All @@ -99,6 +101,7 @@ internal PublishHelper(PSCmdlet cmdlet,
SkipModuleManifestValidate = skipModuleManifestValidate;
_cancellationToken = cancellationToken;
_isNupkgPathSpecified = isNupkgPathSpecified;
_allowInsecureConnection = allowInsecureConnection;
outputDir = System.IO.Path.Combine(System.IO.Path.GetTempPath(), Guid.NewGuid().ToString());
outputNupkgDir = System.IO.Path.Combine(outputDir, "nupkg");
}
Expand Down Expand Up @@ -700,6 +703,7 @@ private bool PushNupkg(string outputNupkgDir, string repoName, string repoUri, N
noSymbols: false,
noServiceEndpoint: false, // enable server endpoint
skipDuplicate: false, // if true-- if a package and version already exists, skip it and continue with the next package in the push, if any.
allowInsecureConnections: _allowInsecureConnection,
logger: NullLogger.Instance // nuget logger
).GetAwaiter().GetResult();
}
Expand Down
9 changes: 8 additions & 1 deletion src/code/PublishPSResource.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,12 @@ public sealed class PublishPSResource : PSCmdlet
[ValidateNotNullOrEmpty]
public string ApiKey { get; set; }

/// <summary>
/// Allows publishing to HTTP NuGet repositories. Defaults to false.
/// </summary>
[Parameter]
public bool AllowInsecureConnection { get; set; } = false;

/// <summary>
/// Specifies the repository to publish to.
/// </summary>
Expand Down Expand Up @@ -185,7 +191,8 @@ protected override void BeginProcessing()
DestinationPath,
SkipModuleManifestValidate,
_cancellationToken,
_isNupkgPathSpecified);
_isNupkgPathSpecified,
AllowInsecureConnection);

_publishHelper.CheckAllParameterPaths();
}
Expand Down
60 changes: 60 additions & 0 deletions test/PublishPSResourceTests/PublishPSResource.Tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,66 @@ Describe "Test Publish-PSResource" -tags 'CI' {
Remove-Item $pkgsToDelete -Recurse -ErrorAction SilentlyContinue
}

It "AllowInsecureConnection is a Boolean parameter defaulting to false in both parameter sets" {
$command = Get-Command Publish-PSResource
$command.Parameters['AllowInsecureConnection'].ParameterType | Should -Be ([bool])
foreach ($parameterSet in $command.ParameterSets) {
$parameterSet.Parameters.Name | Should -Contain 'AllowInsecureConnection'
}

$cmdlet = New-Object Microsoft.PowerShell.PSResourceGet.Cmdlets.PublishPSResource
$cmdlet.AllowInsecureConnection | Should -BeFalse
}

It "Publish a module with AllowInsecureConnection set to <AllowInsecureConnection>" -TestCases @(
@{ AllowInsecureConnection = $true }
@{ AllowInsecureConnection = $false }
) {
param($AllowInsecureConnection)

$version = "1.0.0"
New-ModuleManifest -Path (Join-Path $script:PublishModuleBase "$script:PublishModuleName.psd1") -ModuleVersion $version -Description "$script:PublishModuleName module"

Publish-PSResource -Path $script:PublishModuleBase -Repository $testRepository2 -AllowInsecureConnection $AllowInsecureConnection

$expectedPath = Join-Path $script:repositoryPath2 "$script:PublishModuleName.$version.nupkg"
Test-Path $expectedPath | Should -BeTrue
}

It "Reject HTTP publishing using <ParameterName> with AllowInsecureConnection omitted: <OmitParameter>" -TestCases @(
@{ ParameterName = 'Path'; OmitParameter = $true }
@{ ParameterName = 'Path'; OmitParameter = $false }
@{ ParameterName = 'NupkgPath'; OmitParameter = $true }
@{ ParameterName = 'NupkgPath'; OmitParameter = $false }
) {
param($ParameterName, $OmitParameter)

$repositoryName = 'InsecurePublishTest'
Register-PSResourceRepository -Name $repositoryName -Uri 'http://127.0.0.1:1/v2' -ApiVersion v2
try {
New-ModuleManifest -Path (Join-Path $script:PublishModuleBase "$script:PublishModuleName.psd1") -ModuleVersion '1.0.0' -Description "$script:PublishModuleName module"
$publishParameters = @{ Path = $script:PublishModuleBase }
if ($ParameterName -eq 'NupkgPath') {
Compress-PSResource -Path $script:PublishModuleBase -DestinationPath $script:destinationPath
$publishParameters = @{ NupkgPath = (Join-Path $script:destinationPath "$script:PublishModuleName.1.0.0.nupkg") }
}

if (!$OmitParameter) {
$publishParameters.AllowInsecureConnection = $false
}

$publishErrors = @()
Publish-PSResource @publishParameters -Repository $repositoryName -SkipDependenciesCheck -ErrorAction SilentlyContinue -ErrorVariable publishErrors

$publishErrors.Count | Should -Be 1
$publishErrors[0].FullyQualifiedErrorId | Should -Be 'PushNupkgError,Microsoft.PowerShell.PSResourceGet.Cmdlets.PublishPSResource'
$publishErrors[0].Exception.Message | Should -Match 'HTTPS'
}
finally {
Unregister-PSResourceRepository -Name $repositoryName
}
}

It "Publish module with required module not installed on the local machine using -SkipModuleManifestValidate" {
# Skip the module manifest validation test, which fails from the missing manifest required module.
$testModulePath = Join-Path -Path $TestDrive -ChildPath ModuleWithMissingRequiredModule
Expand Down
Loading