diff --git a/doBuild.ps1 b/doBuild.ps1
index d997cc44a..cc3edcbbb 100644
--- a/doBuild.ps1
+++ b/doBuild.ps1
@@ -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'
@@ -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
diff --git a/src/code/Microsoft.PowerShell.PSResourceGet.csproj b/src/code/Microsoft.PowerShell.PSResourceGet.csproj
index 1f4221845..b5e4e60ee 100644
--- a/src/code/Microsoft.PowerShell.PSResourceGet.csproj
+++ b/src/code/Microsoft.PowerShell.PSResourceGet.csproj
@@ -14,19 +14,19 @@
-
-
-
-
-
-
+
+
+
+
+
+
-
+
-
+
-
+
diff --git a/src/code/ModuleInitAndCleanup.cs b/src/code/ModuleInitAndCleanup.cs
index b3f1992d1..40b1ad182 100644
--- a/src/code/ModuleInitAndCleanup.cs
+++ b/src/code/ModuleInitAndCleanup.cs
@@ -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 s_dependencies;
+ private static readonly Dictionary s_dependencies;
private static readonly AssemblyLoadContextProxy s_proxy;
private static readonly HashSet NetFrameworkLoadFromPath = new HashSet(StringComparer.OrdinalIgnoreCase)
@@ -30,12 +30,13 @@ static UnsafeAssemblyHandler()
{
s_self = Assembly.GetExecutingAssembly();
s_dependencyFolder = Path.Combine(Path.GetDirectoryName(s_self.Location), "dependencies");
- s_dependencies = new HashSet(StringComparer.Ordinal);
+ s_dependencies = new Dictionary(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;
}
}
@@ -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)
diff --git a/src/code/PublishHelper.cs b/src/code/PublishHelper.cs
index 2c121b3db..5a1dea45e 100644
--- a/src/code/PublishHelper.cs
+++ b/src/code/PublishHelper.cs
@@ -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;
@@ -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";
@@ -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");
}
@@ -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();
}
diff --git a/src/code/PublishPSResource.cs b/src/code/PublishPSResource.cs
index e734cf3de..cbf558482 100644
--- a/src/code/PublishPSResource.cs
+++ b/src/code/PublishPSResource.cs
@@ -31,6 +31,12 @@ public sealed class PublishPSResource : PSCmdlet
[ValidateNotNullOrEmpty]
public string ApiKey { get; set; }
+ ///
+ /// Allows publishing to HTTP NuGet repositories. Defaults to false.
+ ///
+ [Parameter]
+ public bool AllowInsecureConnection { get; set; } = false;
+
///
/// Specifies the repository to publish to.
///
@@ -185,7 +191,8 @@ protected override void BeginProcessing()
DestinationPath,
SkipModuleManifestValidate,
_cancellationToken,
- _isNupkgPathSpecified);
+ _isNupkgPathSpecified,
+ AllowInsecureConnection);
_publishHelper.CheckAllParameterPaths();
}
diff --git a/test/PublishPSResourceTests/PublishPSResource.Tests.ps1 b/test/PublishPSResourceTests/PublishPSResource.Tests.ps1
index bb58ddeed..c8965ed24 100644
--- a/test/PublishPSResourceTests/PublishPSResource.Tests.ps1
+++ b/test/PublishPSResourceTests/PublishPSResource.Tests.ps1
@@ -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 " -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 with AllowInsecureConnection omitted: " -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