From a0178b79f100a110072dceaa57f71033d7327855 Mon Sep 17 00:00:00 2001 From: Jordan Borean <jborean93@gmail.com> Date: Wed, 7 Feb 2018 20:58:47 +1100 Subject: [PATCH] win_uri: fixes (#35487) * win_uri: moved away from Invoke-WebRequest and fixed multiple issues * fixes from review --- .../Ansible.ModuleUtils.Legacy.psm1 | 2 + lib/ansible/modules/windows/win_uri.ps1 | 237 ++++++++++---- lib/ansible/modules/windows/win_uri.py | 70 ++-- .../targets/uri/files/testserver.ps1 | 36 --- test/integration/targets/win_uri/aliases | 1 + .../targets/win_uri/defaults/main.yml | 3 + .../targets/win_uri/tasks/main.yml | 14 + .../targets/win_uri/tasks/test.yml | 298 ++++++++++++++++++ test/sanity/pslint/ignore.txt | 1 - 9 files changed, 528 insertions(+), 134 deletions(-) delete mode 100644 test/integration/targets/uri/files/testserver.ps1 create mode 100644 test/integration/targets/win_uri/aliases create mode 100644 test/integration/targets/win_uri/defaults/main.yml create mode 100644 test/integration/targets/win_uri/tasks/main.yml create mode 100644 test/integration/targets/win_uri/tasks/test.yml diff --git a/lib/ansible/module_utils/powershell/Ansible.ModuleUtils.Legacy.psm1 b/lib/ansible/module_utils/powershell/Ansible.ModuleUtils.Legacy.psm1 index a5e7a7c70b3..9dad05728ed 100644 --- a/lib/ansible/module_utils/powershell/Ansible.ModuleUtils.Legacy.psm1 +++ b/lib/ansible/module_utils/powershell/Ansible.ModuleUtils.Legacy.psm1 @@ -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." } diff --git a/lib/ansible/modules/windows/win_uri.ps1 b/lib/ansible/modules/windows/win_uri.ps1 index 5e7ec6b3b6b..6ace1c3cd70 100644 --- a/lib/ansible/modules/windows/win_uri.ps1 +++ b/lib/ansible/modules/windows/win_uri.ps1 @@ -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 } +} + +# 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 ($null -ne $content_type) { + $client.ContentType = $content_type } if ($headers) { - $req_headers = @{} - ForEach ($header in $headers.psobject.properties) { - $req_headers.Add($header.Name, $header.Value) + $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) } + } } - $webrequest_opts.Headers = $req_headers + $client.Headers = $req_headers } 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 (-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 ($body) { - $webrequest_opts.Body = $body - $result.body = $body -} - -if ($dest -and -not $check_mode) { - $webrequest_opts.OutFile = $dest - $webrequest_opts.PassThru = $true - $result.dest = $dest -} - 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 + diff --git a/lib/ansible/modules/windows/win_uri.py b/lib/ansible/modules/windows/win_uri.py index 9d5dc0ab29c..c30c8967bb1 100644 --- a/lib/ansible/modules/windows/win_uri.py +++ b/lib/ansible/modules/windows/win_uri.py @@ -22,12 +22,11 @@ options: url: description: - Supports FTP, HTTP or HTTPS URLs in the form of (ftp|http|https)://host.domain:port/path. - - Also supports file:/// URLs through Invoke-WebRequest. required: yes method: description: - The HTTP Method of the request or response. - choices: [ CONNECT, DELETE, GET, HEAD, OPTIONS, PATCH, POST, PUT, REFRESH, TRACE ] + choices: [ CONNECT, DELETE, GET, HEAD, MERGE, OPTIONS, PATCH, POST, PUT, REFRESH, TRACE ] default: GET content_type: description: @@ -43,16 +42,30 @@ options: description: - Password to use for authentication. version_added: '2.4' + force_basic_auth: + description: + - By default the authentication information is only sent when a webservice + responds to an initial request with a 401 status. Since some basic auth + services do not properly send a 401, logins will fail. + - This option forces the sending of the Basic authentication header upon + the initial request. + type: bool + default: 'no' + version_added: '2.5' dest: description: - Output the response body to a file. version_added: '2.3' headers: description: - - 'Key Value pairs for headers. Example "Host: www.somesite.com"' + - Extra headers to set on the request, see the examples for more details on + how to set this. use_basic_parsing: description: - - This module relies upon 'Invoke-WebRequest', which by default uses the Internet Explorer Engine to parse a webpage. + - As of Ansible 2.5, this option is no longer valid and cannot be changed from C(yes), this option will be removed + in Ansible 2.7. + - Before Ansible 2.5, this module relies upon 'Invoke-WebRequest', which by default uses the Internet Explorer Engine + to parse a webpage. - There's an edge-case where if a user hasn't run IE before, this will fail. - The only advantage to using the Internet Explorer praser is that you can traverse the DOM in a powershell script. - That isn't useful for Ansible, so by default we toggle 'UseBasicParsing'. However, you can toggle that off here. @@ -120,8 +133,17 @@ options: version_added: '2.4' client_cert: description: - - Specifies the client certificate(.pfx) that is used for a secure web request. + - Specifies the client certificate (.pfx) that is used for a secure web request. + - The WinRM connection must be authenticated with C(CredSSP) if the + certificate file is not password protected. + - Other authentication types can set I(client_cert_password) when the cert + is password protected. version_added: '2.4' + client_cert_password: + description: + - The password for the client certificate (.pfx) file that is used for a + secure web request. + version_added: '2.5' notes: - For non-Windows targets, use the M(uri) module instead. author: @@ -161,26 +183,6 @@ url: returned: always type: string sample: https://www.ansible.com -method: - description: The HTTP method used. - returned: always - type: string - sample: GET -content_type: - description: The "content-type" header used. - returned: always - type: string - sample: application/json -use_basic_parsing: - description: The state of the "use_basic_parsing" flag. - returned: always - type: bool - sample: True -body: - description: The content of the body used - returned: when body is specified - type: string - sample: '{"id":1}' status_code: description: The HTTP Status Code of the response. returned: success @@ -191,19 +193,19 @@ status_description: returned: success type: string sample: OK -raw_content: +content: description: The raw content of the HTTP response. - returned: success + returned: success and return_content is True type: string - sample: 'HTTP/1.1 200 OK\nX-XSS-Protection: 1; mode=block\nAlternate-Protocol: 443:quic,p=1\nAlt-Svc: quic="www.google.com:443";' -headers: - description: The Headers of the response. - returned: success - type: dict - sample: {"Content-Type": "application/json"} -raw_content_length: + sample: '{"foo": "bar"}' +content_length: description: The byte size of the response. returned: success type: int sample: 54447 +json: + description: The json structure returned under content as a dictionary + returned: success and Content-Type is "application/json" or "application/javascript" and return_content is True + type: dict + sample: {"this-is-dependent": "on the actual return content"} ''' diff --git a/test/integration/targets/uri/files/testserver.ps1 b/test/integration/targets/uri/files/testserver.ps1 deleted file mode 100644 index 6df4bd05f9e..00000000000 --- a/test/integration/targets/uri/files/testserver.ps1 +++ /dev/null @@ -1,36 +0,0 @@ -param ( - [int]$port = 8000 -) - -$listener = New-Object Net.HttpListener -$listener.Prefixes.Add("http://+:$port/") -$listener.Start() - -try { - while ($listener.IsListening) { - # process received request - $context = $listener.GetContext() - $Request = $context.Request - $Response = $context.Response - #$Response.Headers.Add("Content-Type","text/plain") - - $received = '{0} {1}' -f $Request.httpmethod, $Request.url.localpath - - # is there HTML content for this URL? - $html = $htmlcontents[$received] - if ($html -eq $null) { - $Response.statuscode = 404 - $html = 'Oops, the page is not available!' - } - - # return the HTML to the caller - $buffer = [Text.Encoding]::UTF8.GetBytes($html) - $Response.ContentLength64 = $buffer.length - $Response.OutputStream.Write($buffer, 0, $buffer.length) - - $Response.Close() - } -} finally { - $listener.Stop() - $listener.Close() -} diff --git a/test/integration/targets/win_uri/aliases b/test/integration/targets/win_uri/aliases new file mode 100644 index 00000000000..2854047d09b --- /dev/null +++ b/test/integration/targets/win_uri/aliases @@ -0,0 +1 @@ +windows/ci/group4 diff --git a/test/integration/targets/win_uri/defaults/main.yml b/test/integration/targets/win_uri/defaults/main.yml new file mode 100644 index 00000000000..1a02bcaafe7 --- /dev/null +++ b/test/integration/targets/win_uri/defaults/main.yml @@ -0,0 +1,3 @@ +--- +test_uri_path: C:\ansible\win_uri +httpbin_host: httpbin.org diff --git a/test/integration/targets/win_uri/tasks/main.yml b/test/integration/targets/win_uri/tasks/main.yml new file mode 100644 index 00000000000..7105f5aa477 --- /dev/null +++ b/test/integration/targets/win_uri/tasks/main.yml @@ -0,0 +1,14 @@ +--- +- name: create test directory + win_file: + path: '{{test_uri_path}}' + state: directory + +- block: + - include_tasks: test.yml + + always: + - name: cleanup test directory + win_file: + path: '{{test_uri_path}}' + state: absent diff --git a/test/integration/targets/win_uri/tasks/test.yml b/test/integration/targets/win_uri/tasks/test.yml new file mode 100644 index 00000000000..551dfeeb4bb --- /dev/null +++ b/test/integration/targets/win_uri/tasks/test.yml @@ -0,0 +1,298 @@ +--- +# get with mismatch https +# get with mismatch https and ignore validation + +- name: get request without return_content + win_uri: + url: http://{{httpbin_host}}/get + return_content: no + register: get_request_without_content + +- name: assert get request without return_content + assert: + that: + - not get_request_without_content.changed + - get_request_without_content.content is not defined + - get_request_without_content.json is not defined + - get_request_without_content.status_code == 200 + +- name: get request with xml content + win_uri: + url: http://{{httpbin_host}}/xml + return_content: yes + register: get_request_with_xml_content + +- name: assert get request with xml content + assert: + that: + - not get_request_with_xml_content.changed + - get_request_with_xml_content.content is defined + - get_request_with_xml_content.json is not defined + - get_request_with_xml_content.status_code == 200 + +- name: get request with binary content + win_uri: + url: http://{{httpbin_host}}/image/png + return_content: yes + register: get_request_with_binary_content + +- name: assert get request with binary content + assert: + that: + - not get_request_with_binary_content.changed + - get_request_with_binary_content.content is defined + - get_request_with_binary_content.json is not defined + - get_request_with_xml_content.status_code == 200 + +- name: get request with return_content and dest (check mode) + win_uri: + url: http://{{httpbin_host}}/get + return_content: yes + dest: '{{test_uri_path}}\get.json' + register: get_request_with_dest_check + check_mode: yes + +- name: get stat of downloaded file (check mode) + win_stat: + path: '{{test_uri_path}}\get.json' + register: get_request_with_dest_actual_check + +- name: assert get request with return_content and dest (check mode) + assert: + that: + - get_request_with_dest_check.changed + - get_request_with_dest_check.content is defined + - get_request_with_dest_check.json is defined + - get_request_with_dest_actual_check.stat.exists == False + +- name: get request with return_content and dest + win_uri: + url: http://{{httpbin_host}}/get + return_content: yes + dest: '{{test_uri_path}}\get.json' + register: get_request_with_dest + +- name: get stat of downloaded file + win_stat: + path: '{{test_uri_path}}\get.json' + checksum_algorithm: sha1 + get_checksum: yes + register: get_request_with_dest_actual + +- name: assert get request with return_content and dest + assert: + that: + - get_request_with_dest.changed + - get_request_with_dest.content is defined + - get_request_with_dest.json is defined + - get_request_with_dest_actual.stat.exists == True + - get_request_with_dest_actual.stat.checksum == get_request_with_dest.content|hash('sha1') + +- name: get request with return_content and dest (idempotent) + win_uri: + url: http://{{httpbin_host}}/get + return_content: yes + dest: '{{test_uri_path}}\get.json' + register: get_request_with_dest_again + +- name: assert get request with return_content and dest (idempotent) + assert: + that: + - not get_request_with_dest_again.changed + +- name: post request with return_content, dest and different content + win_uri: + url: http://{{httpbin_host}}/post + method: POST + content_type: application/json + body: '{"foo": "bar"}' + return_content: yes + dest: '{{test_uri_path}}\get.json' + register: post_request_with_different_content + +- name: get stat of downloaded file + win_stat: + path: '{{test_uri_path}}\get.json' + checksum_algorithm: sha1 + get_checksum: yes + register: post_request_with_different_content_actual + +- name: assert post request with return_content, dest and different content + assert: + that: + - post_request_with_different_content.changed + - post_request_with_different_content_actual.stat.exists == True + - post_request_with_different_content_actual.stat.checksum == post_request_with_different_content.content|hash('sha1') + +- name: test redirect without follow_redirects + win_uri: + url: http://{{httpbin_host}}/redirect/2 + follow_redirects: none + status_code: 302 + register: redirect_without_follow + +- name: assert redirect without follow_redirects + assert: + that: + - not redirect_without_follow.changed + - redirect_without_follow.location|default("") == '/relative-redirect/1' + - redirect_without_follow.status_code == 302 + +- name: test redirect with follow_redirects + win_uri: + url: http://{{httpbin_host}}/redirect/2 + follow_redirects: all + register: redirect_with_follow + +- name: assert redirect with follow_redirects + assert: + that: + - not redirect_with_follow.changed + - redirect_with_follow.location is not defined + - redirect_with_follow.status_code == 200 + - redirect_with_follow.response_uri == 'http://{{httpbin_host}}/get' + +- name: get request with redirect of TLS + win_uri: + url: https://{{httpbin_host}}/redirect/2 + follow_redirects: all + register: redirect_with_follow_tls + +- name: assert redirect with redirect of TLS + assert: + that: + - not redirect_with_follow_tls.changed + - redirect_with_follow_tls.location is not defined + - redirect_with_follow_tls.status_code == 200 + - redirect_with_follow_tls.response_uri == 'https://{{httpbin_host}}/get' + +- name: test basic auth + win_uri: + url: http://{{httpbin_host}}/basic-auth/user/passwd + user: user + password: passwd + register: basic_auth + +- name: assert test basic auth + assert: + that: + - not basic_auth.changed + - basic_auth.status_code == 200 + +- name: test basic auth with force auth + win_uri: + url: http://{{httpbin_host}}/hidden-basic-auth/user/passwd + user: user + password: passwd + force_basic_auth: yes + register: basic_auth_forced + +- name: assert test basic auth with forced auth + assert: + that: + - not basic_auth_forced.changed + - basic_auth_forced.status_code == 200 + +- name: test PUT + win_uri: + url: http://{{httpbin_host}}/put + method: PUT + body: foo=bar + return_content: yes + register: put_request + +- name: assert test PUT + assert: + that: + - not put_request.changed + - put_request.status_code == 200 + - put_request.json.data == 'foo=bar' + +- name: test OPTIONS + win_uri: + url: http://{{httpbin_host}}/ + method: OPTIONS + register: option_request + +- name: assert test OPTIONS + assert: + that: + - not option_request.changed + - option_request.status_code == 200 + - 'option_request.allow.split(", ")|sort == ["GET", "HEAD", "OPTIONS"]' + +# SNI Tests + +- name: validate status_codes are correct + win_uri: + url: http://{{httpbin_host}}/status/202 + status_code: + - 202 + method: POST + body: foo + register: status_code_check + +- name: assert validate status_codes are correct + assert: + that: + - not status_code_check.changed + - status_code_check.status_code == 202 + +- name: send JSON body with dict type + win_uri: + url: http://{{httpbin_host}}/post + method: POST + body: + foo: bar + list: + - 1 + - 2 + dict: + foo: bar + headers: + 'Content-Type': 'text/json' + return_content: yes + register: json_as_dict + +- name: set fact of expected json dict + set_fact: + json_as_dict_value: + foo: bar + list: + - 1 + - 2 + dict: + foo: bar + +- name: assert send JSON body with dict type + assert: + that: + - not json_as_dict.changed + - json_as_dict.json.json == json_as_dict_value + - json_as_dict.status_code == 200 + +- name: get request with custom headers + win_uri: + url: http://{{httpbin_host}}/get + headers: + Test-Header: hello + Another-Header: world + return_content: yes + register: get_custom_header + +- name: assert request with custom headers + assert: + that: + - not get_custom_header.changed + - get_custom_header.status_code == 200 + - get_custom_header.json.headers['Test-Header'] == 'hello' + - get_custom_header.json.headers['Another-Header'] == 'world' + +# client cert auth tests + +- name: get request with timeout + win_uri: + url: http://{{httpbin_host}}/delay/10 + timeout: 5 + register: get_with_timeout_fail + failed_when: '"The operation has timed out" not in get_with_timeout_fail.msg' diff --git a/test/sanity/pslint/ignore.txt b/test/sanity/pslint/ignore.txt index ef66e4d6977..7f54725681b 100644 --- a/test/sanity/pslint/ignore.txt +++ b/test/sanity/pslint/ignore.txt @@ -160,7 +160,6 @@ lib/ansible/modules/windows/win_wakeonlan.ps1 PSAvoidUsingCmdletAliases lib/ansible/modules/windows/win_webpicmd.ps1 PSAvoidUsingInvokeExpression lib/ansible/modules/windows/win_webpicmd.ps1 PSPossibleIncorrectComparisonWithNull lib/ansible/modules/windows/win_webpicmd.ps1 PSUseOutputTypeCorrectly -test/integration/targets/uri/files/testserver.ps1 PSPossibleIncorrectComparisonWithNull test/integration/targets/win_audit_rule/library/test_get_audit_rule.ps1 PSAvoidUsingCmdletAliases test/integration/targets/win_dsc/library/test_win_dsc_iis_info.ps1 PSPossibleIncorrectComparisonWithNull test/integration/targets/win_dsc/templates/ANSIBLE_xTestResource.psm1 PSAvoidDefaultValueForMandatoryParameter