Skip to content

Commit

Permalink
win_uri: fixes (#35487)
Browse files Browse the repository at this point in the history
* win_uri: moved away from Invoke-WebRequest and fixed multiple issues

* fixes from review
  • Loading branch information
jborean93 committed Feb 7, 2018
1 parent 63fdc3f commit a0178b7
Show file tree
Hide file tree
Showing 9 changed files with 528 additions and 134 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,8 @@ Function Get-AnsibleParam($obj, $name, $default = $null, $resultobj = @{}, $fail
} elseif ($value -is [string]) {
# Convert string type to real Powershell array
$value = $value.Split(",").Trim()
} elseif ($value -is [int]) {
$value = @($value)
} else {
Fail-Json -obj $resultobj -message "Get-AnsibleParam: Parameter '$name' is not a YAML list."
}
Expand Down
237 changes: 174 additions & 63 deletions lib/ansible/modules/windows/win_uri.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -5,141 +5,252 @@
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)

#Requires -Module Ansible.ModuleUtils.Legacy
#Requires -Module Ansible.ModuleUtils.CamelConversion
#Requires -Module Ansible.ModuleUtils.FileUtil

$ErrorActionPreference = "Stop"

$safe_methods = @("GET", "HEAD")
$content_keys = @("Content", "Images", "InputFields", "Links", "RawContent")

Function ConvertTo-SnakeCase($input_string) {
$snake_case = $input_string -csplit "(?<!^)(?=[A-Z])" -join "_"
return $snake_case.ToLower()
}

$params = Parse-Args -arguments $args -supports_check_mode $true
$check_mode = Get-AnsibleParam -obj $params -name "_ansible_check_mode" -type "bool" -default $false

$url = Get-AnsibleParam -obj $params -name "url" -type "str" -failifempty $true
$method = Get-AnsibleParam -obj $params "method" -type "str" -default "GET" -validateset "CONNECT","DELETE","GET","HEAD","OPTIONS","PATCH","POST","PUT","REFRESH","TRACE"
$method = Get-AnsibleParam -obj $params "method" -type "str" -default "GET" -validateset "CONNECT","DELETE","GET","HEAD","MERGE","OPTIONS","PATCH","POST","PUT","REFRESH","TRACE"
$content_type = Get-AnsibleParam -obj $params -name "content_type" -type "str"
$headers = Get-AnsibleParam -obj $params -name "headers" -type="dict"
$body = Get-AnsibleParam -obj $params -name "body" -type "dict"
$headers = Get-AnsibleParam -obj $params -name "headers"
$body = Get-AnsibleParam -obj $params -name "body"
$dest = Get-AnsibleParam -obj $params -name "dest" -type "path"

$user = Get-AnsibleParam -obj $params -name "user" -type "str"
$password = Get-AnsibleParam -obj $params -name "password" -type "str"
$force_basic_auth = Get-AnsibleParam -obj $params -name "force_basic_auth" -type "bool" -default $false

$creates = Get-AnsibleParam -obj $params -name "creates" -type "path"
$removes = Get-AnsibleParam -obj $params -name "removes" -type "path"

$follow_redirects = Get-AnsibleParam -obj $params -name "follow_redirects" -type "str" -default "safe" -validateset "all","none","safe"
$maximum_redirection = Get-AnsibleParam -obj $params -name "maximum_redirection" -type "int" -default 5
$maximum_redirection = Get-AnsibleParam -obj $params -name "maximum_redirection" -type "int" -default 50
$return_content = Get-AnsibleParam -obj $params -name "return_content" -type "bool" -default $false
$status_code = Get-AnsibleParam -obj $params -name "status_code" -type "list" -default @(200)
$timeout = Get-AnsibleParam -obj $params -name "timeout" -type "int" -default 30
$use_basic_parsing = Get-AnsibleParam -obj $params -name "use_basic_parsing" -type "bool" -default $true
$use_basic_parsing = Get-AnsibleParam -obj $params -name "use_basic_parsing" -type "bool"
$validate_certs = Get-AnsibleParam -obj $params -name "validate_certs" -type "bool" -default $true
$client_cert = Get-AnsibleParam -obj $params -name "client_cert" -type "path"
$client_cert_password = Get-AnsibleParam -obj $params -name "client_cert_password" -type "str"

if ($creates -and (Test-Path -Path $creates)) {
if ($creates -and (Test-AnsiblePath -Path $creates)) {
$result.skipped = $true
Exit-Json -obj $result -message "The 'creates' file or directory ($creates) already exists."
}

if ($removes -and -not (Test-Path -Path $removes)) {
if ($removes -and -not (Test-AnsiblePath -Path $removes)) {
$result.skipped = $true
Exit-Json -obj $result -message "The 'removes' file or directory ($removes) does not exist."
}

$result = @{
changed = $false
content_type = $content_type
method = $method
url = $url
use_basic_parsing = $use_basic_parsing
}

if ($use_basic_parsing) {
Add-DeprecationWarning -obj $result -message "Since Ansible 2.5, use_basic_parsing does not change any behaviour, this option will be removed" -version 2.7
}

$client = [System.Net.WebRequest]::Create($url)
$client.Method = $method
$client.Timeout = $timeout * 1000

# Disable redirection if requested
switch($follow_redirects) {
"none" {
$maximum_redirection = 0
$client.AllowAutoRedirect = $false
}
"safe" {
if ($safe_methods -notcontains $method) {
$maximum_redirection = 0
if (@("GET", "HEAD") -notcontains $method) {
$client.AllowAutoRedirect = $false
} else {
$client.AllowAutoRedirect = $true
}
}
default {
$client.AllowAutoRedirect = $true
}
}

$webrequest_opts = @{
ContentType = $content_type
ErrorAction = "SilentlyContinue"
MaximumRedirection = $maximum_redirection
Method = $method
TimeoutSec = $timeout
Uri = $url
UseBasicParsing = $use_basic_parsing
if ($maximum_redirection -eq 0) {
# 0 is not a valid option, need to disable redirection through AllowAutoRedirect
$client.AllowAutoRedirect = $false
} else {
$client.MaximumAutomaticRedirections = $maximum_redirection
}

if (-not $validate_certs) {
$PSDefaultParameterValues.Add("Invoke-WebRequest:SkipCertificateCheck", $true)
[System.Net.ServicePointManager]::ServerCertificateValidationCallback = { $true }
}

if ($headers) {
$req_headers = @{}
ForEach ($header in $headers.psobject.properties) {
$req_headers.Add($header.Name, $header.Value)
}
$webrequest_opts.Headers = $req_headers
# Enable TLS1.1/TLS1.2 if they're available but disabled (eg. .NET 4.5)
$security_protcols = [Net.ServicePointManager]::SecurityProtocol -bor [Net.SecurityProtocolType]::SystemDefault
if ([Net.SecurityProtocolType].GetMember("Tls11").Count -gt 0) {
$security_protcols = $security_protcols -bor [Net.SecurityProtocolType]::Tls11
}
if ([Net.SecurityProtocolType].GetMember("Tls12").Count -gt 0) {
$security_protcols = $security_protcols -bor [Net.SecurityProtocolType]::Tls12
}
[Net.ServicePointManager]::SecurityProtocol = $security_protcols

if ($client_cert) {
Try {
$webrequest_opts.Certificate = Get-PfxCertificate -FilePath $client_cert
} Catch {
Fail-Json -obj $result -message "Failed to read client certificate '$client_cert'"
}

if ($null -ne $content_type) {
$client.ContentType = $content_type
}

if ($body) {
$webrequest_opts.Body = $body
$result.body = $body
if ($headers) {
$req_headers = New-Object -TypeName System.Net.WebHeaderCollection
foreach ($header in $headers.GetEnumerator()) {
# some headers need to be set on the property itself
switch ($header.Name) {
Accept { $client.Accept = $header.Value }
Connection { $client.Connection = $header.Value }
Content-Length { $client.ContentLength = $header.Value }
Content-Type { $client.ContentType = $header.Value }
Expect { $client.Expect = $header.Value }
Date { $client.Date = $header.Value }
Host { $client.Host = $header.Value }
If-Modified-Since { $client.IfModifiedSince = $header.Value }
Range { $client.AddRange($header.Value) }
Referer { $client.Referer = $header.Value }
Transfer-Encoding {
$client.SendChunked = $true
$client.TransferEncoding = $header.Value
}
User-Agent { $client.UserAgent = $header.Value }
default { $req_headers.Add($header.Name, $header.Value) }
}
}
$client.Headers = $req_headers
}

if ($dest -and -not $check_mode) {
$webrequest_opts.OutFile = $dest
$webrequest_opts.PassThru = $true
$result.dest = $dest
if ($client_cert) {
if (-not (Test-AnsiblePath -Path $client_cert)) {
Fail-Json -obj $result -message "Client certificate '$client_cert' does not exist"
}
try {
$certs = New-Object -TypeName System.Security.Cryptography.X509Certificates.X509Certificate2Collection -ArgumentList $client_cert, $client_cert_password
$client.ClientCertificates = $certs
} catch [System.Security.Cryptography.CryptographicException] {
Fail-Json -obj $result -message "Failed to read client certificate '$client_cert': $($_.Exception.Message)"
} catch {
Fail-Json -obj $result -message "Unhandled exception when reading client certificate at '$client_cert': $($_.Exception.Message)"
}
}

if ($user -and $password) {
$webrequest_opts.Credential = New-Object System.Management.Automation.PSCredential($user, $($password | ConvertTo-SecureString -AsPlainText -Force))
if ($force_basic_auth) {
$basic_value = [Convert]::ToBase64String([System.Text.Encoding]::ASCII.GetBytes("$($user):$($password)"))
$client.Headers.Add("Authorization", "Basic $basic_value")
} else {
$sec_password = ConvertTo-SecureString -String $password -AsPlainText -Force
$credential = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $user, $sec_password
$client.Credentials = $credential
}
} elseif ($user -or $password) {
Add-Warning -obj $result -message "Both 'user' and 'password' parameters are required together, skipping authentication"
}

try {
$response = Invoke-WebRequest @webrequest_opts
} catch {
Fail-Json $result $_.Exception.Message
if ($null -ne $body) {
if ($body -is [Hashtable]) {
$body_string = ConvertTo-Json -InputObject $body -Compress
} elseif ($body -isnot [String]) {
$body_string = $body.ToString()
} else {
$body_string = $body
}
$buffer = [System.Text.Encoding]::UTF8.GetBytes($body_string)

$req_st = $client.GetRequestStream()
try {
$req_st.Write($buffer, 0, $buffer.Length)
} finally {
$req_st.Flush()
$req_st.Close()
}
}

# TODO: When writing to a file, this is not idempotent !
# FIXME: Assume a change when we are writing to a file
if ($dest) {
$result.changed = $true
try {
$response = $client.GetResponse()
} catch [System.Net.ProtocolViolationException] {
Fail-Json -obj $result -message "ProtocolViolationException when sending web request: $($_.Exception.Message)"
} catch [System.Net.WebException] {
Fail-Json -obj $result -message "WebException occurred when sending web request: $($_.Exception.Message)"
} catch {
Fail-Json -obj $result -message "Unhandled exception occured when sending web request. Exception: $($_.Exception.Message)"
}

ForEach ($prop in $response.psobject.properties) {
if ($content_keys -contains $prop.Name -and -not $return_content) {
continue
}
$result_key = ConvertTo-SnakeCase $prop.Name
$result_key = Convert-StringToSnakeCase -string $prop.Name
$result.$result_key = $prop.Value
}

# manually get the headers as not all of them are in the response properties
foreach ($header_key in $response.Headers.GetEnumerator()) {
$header_value = $response.Headers[$header_key]
$header_key = $header_key.Replace("-", "") # replace - with _ for snake conversion
$header_key = Convert-StringToSnakeCase -string $header_key
$result.$header_key = $header_value
}

if ($status_code -notcontains $response.StatusCode) {
Fail-Json -obj $result -message "Status code of request '$($response.StatusCode)' is not in list of valid status codes $status_code."
}

# we only care about the return body if we need to return the content or create a file
if ($return_content -or $dest) {
$resp_st = $response.GetResponseStream()

# copy to a MemoryStream so we can read it multiple times
$memory_st = New-Object -TypeName System.IO.MemoryStream
try {
$resp_st.CopyTo($memory_st)
$resp_st.Close()

if ($return_content) {
$memory_st.Seek(0, [System.IO.SeekOrigin]::Begin)
$content_bytes = $memory_st.ToArray()
$result.content = [System.Text.Encoding]::UTF8.GetString($content_bytes)
if ($result.ContainsKey("content_type") -and $result.content_type -in @("application/json", "application/javascript")) {
$result.json = ConvertFrom-Json -InputObject $result.content
}
}

if ($dest) {
$memory_st.Seek(0, [System.IO.SeekOrigin]::Begin)
$changed = $true

if (Test-AnsiblePath -Path $dest) {
$actual_checksum = Get-FileChecksum -path $dest -algorithm "sha1"

$sp = New-Object -TypeName System.Security.Cryptography.SHA1CryptoServiceProvider
$content_checksum = [System.BitConverter]::ToString($sp.ComputeHash($memory_st)).Replace("-", "").ToLower()

if ($actual_checksum -eq $content_checksum) {
$changed = $false
}
}

$result.changed = $changed
if ($changed -and (-not $check_mode)) {
$memory_st.Seek(0, [System.IO.SeekOrigin]::Begin)
$file_stream = [System.IO.File]::Create($dest)
try {
$memory_st.CopyTo($file_stream)
} finally {
$file_stream.Flush()
$file_stream.Close()
}
}
}
} finally {
$memory_st.Close()
}
}

Exit-Json -obj $result

0 comments on commit a0178b7

Please sign in to comment.