From ed68c861d45ae4a96c6306a0f876bc6cfebf93ad Mon Sep 17 00:00:00 2001 From: CarloToso <105941898+CarloToso@users.noreply.github.com> Date: Fri, 10 Feb 2023 12:14:29 +0100 Subject: [PATCH] Fix redirection for -CustomMethod "POST" in WebCmdlets (#19111) --- .../Common/WebRequestPSCmdlet.Common.cs | 19 ++++---- .../WebCmdlets.Tests.ps1 | 47 +++++++++++++++++-- 2 files changed, 53 insertions(+), 13 deletions(-) diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/Common/WebRequestPSCmdlet.Common.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/Common/WebRequestPSCmdlet.Common.cs index a9c5934ecd..a4a924079f 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/Common/WebRequestPSCmdlet.Common.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/Common/WebRequestPSCmdlet.Common.cs @@ -1211,16 +1211,16 @@ namespace Microsoft.PowerShell.Commands // Add 1 to account for the first request. int totalRequests = WebSession.MaximumRetryCount + 1; - HttpRequestMessage req = request; + HttpRequestMessage currentRequest = request; HttpResponseMessage response = null; do { // Track the current URI being used by various requests and re-requests. - Uri currentUri = req.RequestUri; + Uri currentUri = currentRequest.RequestUri; _cancelToken = new CancellationTokenSource(); - response = client.SendAsync(req, HttpCompletionOption.ResponseHeadersRead, _cancelToken.Token).GetAwaiter().GetResult(); + response = client.SendAsync(currentRequest, HttpCompletionOption.ResponseHeadersRead, _cancelToken.Token).GetAwaiter().GetResult(); if (handleRedirect && WebSession.MaximumRedirection is not 0 @@ -1236,13 +1236,12 @@ namespace Microsoft.PowerShell.Commands WebSession.MaximumRedirection--; } - // For selected redirects that used POST, GET must be used with the - // redirected Location. - // Since GET is the default; POST only occurs when -Method POST is used. - if (Method == WebRequestMethod.Post && IsRedirectToGet(response.StatusCode)) + // For selected redirects, GET must be used with the redirected Location. + if (currentRequest.Method == HttpMethod.Post && IsRedirectToGet(response.StatusCode)) { // See https://msdn.microsoft.com/library/system.net.httpstatuscode(v=vs.110).aspx Method = WebRequestMethod.Get; + CustomMethod = string.Empty; } currentUri = new Uri(request.RequestUri, response.Headers.Location); @@ -1327,9 +1326,9 @@ namespace Microsoft.PowerShell.Commands _cancelToken.Cancel(); _cancelToken = null; - req.Dispose(); - req = GetRequest(currentUri); - FillRequestStream(req); + currentRequest.Dispose(); + currentRequest = GetRequest(currentUri); + FillRequestStream(currentRequest); } totalRequests--; diff --git a/test/powershell/Modules/Microsoft.PowerShell.Utility/WebCmdlets.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Utility/WebCmdlets.Tests.ps1 index 35262d8bf4..7ff5867130 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Utility/WebCmdlets.Tests.ps1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Utility/WebCmdlets.Tests.ps1 @@ -132,6 +132,10 @@ function ExecuteRedirectRequest { [string] $Method = 'GET', + [ValidateSet('POST')] + [string] + $CustomMethod, + [switch] $PreserveAuthorizationOnRedirect, @@ -146,12 +150,20 @@ function ExecuteRedirectRequest { if ($Cmdlet -eq 'Invoke-WebRequest') { if ($MaximumRedirection -gt 0) { $result.Output = Invoke-WebRequest -Uri $uri -Headers $headers -PreserveAuthorizationOnRedirect:$PreserveAuthorizationOnRedirect.IsPresent -Method $Method -MaximumRedirection:$MaximumRedirection + } elseif ($CustomMethod) { + $result.Output = Invoke-WebRequest -Uri $uri -Headers $headers -PreserveAuthorizationOnRedirect:$PreserveAuthorizationOnRedirect.IsPresent -CustomMethod $CustomMethod } else { $result.Output = Invoke-WebRequest -Uri $uri -Headers $headers -PreserveAuthorizationOnRedirect:$PreserveAuthorizationOnRedirect.IsPresent -Method $Method } $result.Content = $result.Output.Content | ConvertFrom-Json } else { - $result.Output = Invoke-RestMethod -Uri $uri -Headers $headers -PreserveAuthorizationOnRedirect:$PreserveAuthorizationOnRedirect.IsPresent -Method $Method + if ($MaximumRedirection -gt 0) { + $result.Output = Invoke-RestMethod -Uri $uri -Headers $headers -PreserveAuthorizationOnRedirect:$PreserveAuthorizationOnRedirect.IsPresent -Method $Method -MaximumRedirection:$MaximumRedirection + } elseif ($CustomMethod) { + $result.Output = Invoke-RestMethod -Uri $uri -Headers $headers -PreserveAuthorizationOnRedirect:$PreserveAuthorizationOnRedirect.IsPresent -CustomMethod $CustomMethod + } else { + $result.Output = Invoke-RestMethod -Uri $uri -Headers $headers -PreserveAuthorizationOnRedirect:$PreserveAuthorizationOnRedirect.IsPresent -Method $Method + } # NOTE: $result.Output should already be a PSObject (Invoke-RestMethod converts the returned json automatically) # so simply reference $result.Output $result.Content = $result.Output @@ -986,7 +998,22 @@ Describe "Invoke-WebRequest tests" -Tags "Feature", "RequireAdminOnWindows" { $response.Error | Should -BeNullOrEmpty # ensure user-agent is present (i.e., no false positives ) $response.Content.Headers."User-Agent" | Should -Not -BeNullOrEmpty - # ensure Authorization header has been removed. + # ensure Authorization header has been preserved. + $response.Content.Headers."Authorization" | Should -BeExactly 'test' + # ensure POST was changed to GET for selected redirections and remains as POST for others. + $response.Content.Method | Should -Be $redirectedMethod + } + + It "Validates Invoke-WebRequest -PreserveAuthorizationOnRedirect -CustomMethod POST keeps the authorization header redirects and switches from POST to GET when it handles the redirect: " -TestCases $redirectTests { + param($redirectType, $redirectedMethod) + $uri = Get-WebListenerUrl -Test 'Redirect' -Query @{type = $redirectType} + + $response = ExecuteRedirectRequest -PreserveAuthorizationOnRedirect -Uri $uri -CustomMethod 'POST' + + $response.Error | Should -BeNullOrEmpty + # ensure user-agent is present (i.e., no false positives ) + $response.Content.Headers."User-Agent" | Should -Not -BeNullOrEmpty + # ensure Authorization header has been preserved. $response.Content.Headers."Authorization" | Should -BeExactly 'test' # ensure POST was changed to GET for selected redirections and remains as POST for others. $response.Content.Method | Should -Be $redirectedMethod @@ -2690,7 +2717,21 @@ Describe "Invoke-RestMethod tests" -Tags "Feature", "RequireAdminOnWindows" { $response.Error | Should -BeNullOrEmpty # ensure user-agent is present (i.e., no false positives ) $response.Content.Headers."User-Agent" | Should -Not -BeNullOrEmpty - # ensure Authorization header has been removed. + # ensure Authorization header has been preserved. + $response.Content.Headers."Authorization" | Should -BeExactly 'test' + # ensure POST was changed to GET for selected redirections and remains as POST for others. + $response.Content.Method | Should -Be $redirectedMethod + } + + It "Validates Invoke-RestMethod -PreserveAuthorizationOnRedirect -CustomMethod POST keeps the authorization header redirects and switches from POST to GET when it handles the redirect: " -TestCases $redirectTests { + param($redirectType, $redirectedMethod) + $uri = Get-WebListenerUrl -Test 'Redirect' -Query @{type = $redirectType} + $response = ExecuteRedirectRequest -PreserveAuthorizationOnRedirect -Cmdlet 'Invoke-RestMethod' -Uri $uri -CustomMethod 'POST' + + $response.Error | Should -BeNullOrEmpty + # ensure user-agent is present (i.e., no false positives ) + $response.Content.Headers."User-Agent" | Should -Not -BeNullOrEmpty + # ensure Authorization header has been preserved. $response.Content.Headers."Authorization" | Should -BeExactly 'test' # ensure POST was changed to GET for selected redirections and remains as POST for others. $response.Content.Method | Should -Be $redirectedMethod