mirror of
https://github.com/saltstack/salt.git
synced 2025-04-16 09:40:20 +00:00
Merge 3006.x into master
This commit is contained in:
commit
02c3b89e40
50 changed files with 486 additions and 412 deletions
1
changelog/65643.security.md
Normal file
1
changelog/65643.security.md
Normal file
|
@ -0,0 +1 @@
|
|||
Bump to `cryptography==41.0.7` due to https://github.com/advisories/GHSA-jfhm-5ghh-2f97
|
|
@ -20,7 +20,7 @@ charset-normalizer==3.2.0
|
|||
# via
|
||||
# -c requirements/static/ci/py3.10/linux.txt
|
||||
# requests
|
||||
cryptography==41.0.5
|
||||
cryptography==41.0.7
|
||||
# via
|
||||
# -c requirements/static/ci/py3.10/linux.txt
|
||||
# pyspnego
|
||||
|
|
|
@ -89,7 +89,7 @@ contextvars==2.4
|
|||
# -r requirements/base.txt
|
||||
croniter==1.3.15 ; sys_platform != "win32"
|
||||
# via -r requirements/static/ci/common.in
|
||||
cryptography==41.0.5
|
||||
cryptography==41.0.7
|
||||
# via
|
||||
# -c requirements/static/ci/../pkg/py3.10/darwin.txt
|
||||
# -r requirements/base.txt
|
||||
|
|
|
@ -88,7 +88,7 @@ contextvars==2.4
|
|||
# -r requirements/base.txt
|
||||
croniter==1.3.15 ; sys_platform != "win32"
|
||||
# via -r requirements/static/ci/common.in
|
||||
cryptography==41.0.5
|
||||
cryptography==41.0.7
|
||||
# via
|
||||
# -c requirements/static/ci/../pkg/py3.10/freebsd.txt
|
||||
# -r requirements/base.txt
|
||||
|
|
|
@ -102,7 +102,7 @@ contextvars==2.4
|
|||
# -r requirements/base.txt
|
||||
croniter==1.3.15 ; sys_platform != "win32"
|
||||
# via -r requirements/static/ci/common.in
|
||||
cryptography==41.0.5
|
||||
cryptography==41.0.7
|
||||
# via
|
||||
# -c requirements/static/ci/../pkg/py3.10/linux.txt
|
||||
# -r requirements/base.txt
|
||||
|
|
|
@ -85,7 +85,7 @@ contextvars==2.4
|
|||
# via
|
||||
# -c requirements/static/ci/../pkg/py3.10/windows.txt
|
||||
# -r requirements/base.txt
|
||||
cryptography==41.0.5
|
||||
cryptography==41.0.7
|
||||
# via
|
||||
# -c requirements/static/ci/../pkg/py3.10/windows.txt
|
||||
# -r requirements/base.txt
|
||||
|
|
|
@ -20,7 +20,7 @@ charset-normalizer==3.2.0
|
|||
# via
|
||||
# -c requirements/static/ci/py3.11/linux.txt
|
||||
# requests
|
||||
cryptography==41.0.5
|
||||
cryptography==41.0.7
|
||||
# via
|
||||
# -c requirements/static/ci/py3.11/linux.txt
|
||||
# pyspnego
|
||||
|
|
|
@ -91,7 +91,7 @@ contextvars==2.4
|
|||
# -r requirements/base.txt
|
||||
croniter==1.3.15 ; sys_platform != "win32"
|
||||
# via -r requirements/static/ci/common.in
|
||||
cryptography==41.0.5
|
||||
cryptography==41.0.7
|
||||
# via
|
||||
# -c requirements/static/ci/../pkg/py3.11/darwin.txt
|
||||
# -r requirements/base.txt
|
||||
|
|
|
@ -90,7 +90,7 @@ contextvars==2.4
|
|||
# -r requirements/base.txt
|
||||
croniter==1.3.15 ; sys_platform != "win32"
|
||||
# via -r requirements/static/ci/common.in
|
||||
cryptography==41.0.5
|
||||
cryptography==41.0.7
|
||||
# via
|
||||
# -c requirements/static/ci/../pkg/py3.11/freebsd.txt
|
||||
# -r requirements/base.txt
|
||||
|
|
|
@ -104,7 +104,7 @@ contextvars==2.4
|
|||
# -r requirements/base.txt
|
||||
croniter==1.3.15 ; sys_platform != "win32"
|
||||
# via -r requirements/static/ci/common.in
|
||||
cryptography==41.0.5
|
||||
cryptography==41.0.7
|
||||
# via
|
||||
# -c requirements/static/ci/../pkg/py3.11/linux.txt
|
||||
# -r requirements/base.txt
|
||||
|
|
|
@ -87,7 +87,7 @@ contextvars==2.4
|
|||
# via
|
||||
# -c requirements/static/ci/../pkg/py3.11/windows.txt
|
||||
# -r requirements/base.txt
|
||||
cryptography==41.0.5
|
||||
cryptography==41.0.7
|
||||
# via
|
||||
# -c requirements/static/ci/../pkg/py3.11/windows.txt
|
||||
# -r requirements/base.txt
|
||||
|
|
|
@ -126,7 +126,7 @@ croniter==1.3.15 ; sys_platform != "win32"
|
|||
# via
|
||||
# -c requirements/static/ci/py3.12/linux.txt
|
||||
# -r requirements/static/ci/common.in
|
||||
cryptography==41.0.5
|
||||
cryptography==41.0.7
|
||||
# via
|
||||
# -c requirements/static/ci/../pkg/py3.12/linux.txt
|
||||
# -c requirements/static/ci/py3.12/linux.txt
|
||||
|
|
|
@ -91,7 +91,7 @@ contextvars==2.4
|
|||
# -r requirements/base.txt
|
||||
croniter==1.3.15 ; sys_platform != "win32"
|
||||
# via -r requirements/static/ci/common.in
|
||||
cryptography==41.0.5
|
||||
cryptography==41.0.7
|
||||
# via
|
||||
# -c requirements/static/ci/../pkg/py3.12/darwin.txt
|
||||
# -r requirements/base.txt
|
||||
|
|
|
@ -41,7 +41,7 @@ contextvars==2.4
|
|||
# via
|
||||
# -c requirements/static/ci/py3.12/linux.txt
|
||||
# -r requirements/base.txt
|
||||
cryptography==41.0.5
|
||||
cryptography==41.0.7
|
||||
# via
|
||||
# -c requirements/static/ci/py3.12/linux.txt
|
||||
# -r requirements/base.txt
|
||||
|
|
|
@ -90,7 +90,7 @@ contextvars==2.4
|
|||
# -r requirements/base.txt
|
||||
croniter==1.3.15 ; sys_platform != "win32"
|
||||
# via -r requirements/static/ci/common.in
|
||||
cryptography==41.0.5
|
||||
cryptography==41.0.7
|
||||
# via
|
||||
# -c requirements/static/ci/../pkg/py3.12/freebsd.txt
|
||||
# -r requirements/base.txt
|
||||
|
|
|
@ -143,7 +143,7 @@ croniter==1.3.15 ; sys_platform != "win32"
|
|||
# via
|
||||
# -c requirements/static/ci/py3.12/linux.txt
|
||||
# -r requirements/static/ci/common.in
|
||||
cryptography==41.0.5
|
||||
cryptography==41.0.7
|
||||
# via
|
||||
# -c requirements/static/ci/../pkg/py3.12/linux.txt
|
||||
# -c requirements/static/ci/py3.12/linux.txt
|
||||
|
|
|
@ -104,7 +104,7 @@ contextvars==2.4
|
|||
# -r requirements/base.txt
|
||||
croniter==1.3.15 ; sys_platform != "win32"
|
||||
# via -r requirements/static/ci/common.in
|
||||
cryptography==41.0.5
|
||||
cryptography==41.0.7
|
||||
# via
|
||||
# -c requirements/static/ci/../pkg/py3.12/linux.txt
|
||||
# -r requirements/base.txt
|
||||
|
|
|
@ -98,7 +98,7 @@ contextvars==2.4
|
|||
# via
|
||||
# -c requirements/static/ci/../pkg/py3.11/windows.txt
|
||||
# -r requirements/base.txt
|
||||
cryptography==41.0.5
|
||||
cryptography==41.0.7
|
||||
# via
|
||||
# -c requirements/static/ci/../pkg/py3.11/windows.txt
|
||||
# -r requirements/base.txt
|
||||
|
|
|
@ -20,7 +20,7 @@ charset-normalizer==3.2.0
|
|||
# via
|
||||
# -c requirements/static/ci/py3.8/linux.txt
|
||||
# requests
|
||||
cryptography==41.0.5
|
||||
cryptography==41.0.7
|
||||
# via
|
||||
# -c requirements/static/ci/py3.8/linux.txt
|
||||
# pyspnego
|
||||
|
|
|
@ -88,7 +88,7 @@ contextvars==2.4
|
|||
# -r requirements/base.txt
|
||||
croniter==1.3.15 ; sys_platform != "win32"
|
||||
# via -r requirements/static/ci/common.in
|
||||
cryptography==41.0.5
|
||||
cryptography==41.0.7
|
||||
# via
|
||||
# -c requirements/static/ci/../pkg/py3.8/freebsd.txt
|
||||
# -r requirements/base.txt
|
||||
|
|
|
@ -102,7 +102,7 @@ contextvars==2.4
|
|||
# -r requirements/base.txt
|
||||
croniter==1.3.15 ; sys_platform != "win32"
|
||||
# via -r requirements/static/ci/common.in
|
||||
cryptography==41.0.5
|
||||
cryptography==41.0.7
|
||||
# via
|
||||
# -c requirements/static/ci/../pkg/py3.8/linux.txt
|
||||
# -r requirements/base.txt
|
||||
|
|
|
@ -85,7 +85,7 @@ contextvars==2.4
|
|||
# via
|
||||
# -c requirements/static/ci/../pkg/py3.8/windows.txt
|
||||
# -r requirements/base.txt
|
||||
cryptography==41.0.5
|
||||
cryptography==41.0.7
|
||||
# via
|
||||
# -c requirements/static/ci/../pkg/py3.8/windows.txt
|
||||
# -r requirements/base.txt
|
||||
|
|
|
@ -20,7 +20,7 @@ charset-normalizer==3.2.0
|
|||
# via
|
||||
# -c requirements/static/ci/py3.9/linux.txt
|
||||
# requests
|
||||
cryptography==41.0.5
|
||||
cryptography==41.0.7
|
||||
# via
|
||||
# -c requirements/static/ci/py3.9/linux.txt
|
||||
# pyspnego
|
||||
|
|
|
@ -89,7 +89,7 @@ contextvars==2.4
|
|||
# -r requirements/base.txt
|
||||
croniter==1.3.15 ; sys_platform != "win32"
|
||||
# via -r requirements/static/ci/common.in
|
||||
cryptography==41.0.5
|
||||
cryptography==41.0.7
|
||||
# via
|
||||
# -c requirements/static/ci/../pkg/py3.9/darwin.txt
|
||||
# -r requirements/base.txt
|
||||
|
|
|
@ -88,7 +88,7 @@ contextvars==2.4
|
|||
# -r requirements/base.txt
|
||||
croniter==1.3.15 ; sys_platform != "win32"
|
||||
# via -r requirements/static/ci/common.in
|
||||
cryptography==41.0.5
|
||||
cryptography==41.0.7
|
||||
# via
|
||||
# -c requirements/static/ci/../pkg/py3.9/freebsd.txt
|
||||
# -r requirements/base.txt
|
||||
|
|
|
@ -102,7 +102,7 @@ contextvars==2.4
|
|||
# -r requirements/base.txt
|
||||
croniter==1.3.15 ; sys_platform != "win32"
|
||||
# via -r requirements/static/ci/common.in
|
||||
cryptography==41.0.5
|
||||
cryptography==41.0.7
|
||||
# via
|
||||
# -c requirements/static/ci/../pkg/py3.9/linux.txt
|
||||
# -r requirements/base.txt
|
||||
|
|
|
@ -85,7 +85,7 @@ contextvars==2.4
|
|||
# via
|
||||
# -c requirements/static/ci/../pkg/py3.9/windows.txt
|
||||
# -r requirements/base.txt
|
||||
cryptography==41.0.5
|
||||
cryptography==41.0.7
|
||||
# via
|
||||
# -c requirements/static/ci/../pkg/py3.9/windows.txt
|
||||
# -r requirements/base.txt
|
||||
|
|
|
@ -18,7 +18,7 @@ cherrypy==18.8.0
|
|||
# via -r requirements/base.txt
|
||||
contextvars==2.4
|
||||
# via -r requirements/base.txt
|
||||
cryptography==41.0.5
|
||||
cryptography==41.0.7
|
||||
# via
|
||||
# -r requirements/base.txt
|
||||
# pyopenssl
|
||||
|
|
|
@ -18,7 +18,7 @@ cherrypy==18.8.0
|
|||
# via -r requirements/base.txt
|
||||
contextvars==2.4
|
||||
# via -r requirements/base.txt
|
||||
cryptography==41.0.5
|
||||
cryptography==41.0.7
|
||||
# via
|
||||
# -r requirements/base.txt
|
||||
# pyopenssl
|
||||
|
|
|
@ -18,7 +18,7 @@ cherrypy==18.8.0
|
|||
# via -r requirements/base.txt
|
||||
contextvars==2.4
|
||||
# via -r requirements/base.txt
|
||||
cryptography==41.0.5
|
||||
cryptography==41.0.7
|
||||
# via
|
||||
# -r requirements/base.txt
|
||||
# pyopenssl
|
||||
|
|
|
@ -22,7 +22,7 @@ clr-loader==0.2.4
|
|||
# via pythonnet
|
||||
contextvars==2.4
|
||||
# via -r requirements/base.txt
|
||||
cryptography==41.0.5
|
||||
cryptography==41.0.7
|
||||
# via
|
||||
# -r requirements/base.txt
|
||||
# pyopenssl
|
||||
|
|
|
@ -20,7 +20,7 @@ cherrypy==18.8.0
|
|||
# via -r requirements/base.txt
|
||||
contextvars==2.4
|
||||
# via -r requirements/base.txt
|
||||
cryptography==41.0.5
|
||||
cryptography==41.0.7
|
||||
# via
|
||||
# -r requirements/base.txt
|
||||
# pyopenssl
|
||||
|
|
|
@ -20,7 +20,7 @@ cherrypy==18.8.0
|
|||
# via -r requirements/base.txt
|
||||
contextvars==2.4
|
||||
# via -r requirements/base.txt
|
||||
cryptography==41.0.5
|
||||
cryptography==41.0.7
|
||||
# via
|
||||
# -r requirements/base.txt
|
||||
# pyopenssl
|
||||
|
|
|
@ -20,7 +20,7 @@ cherrypy==18.8.0
|
|||
# via -r requirements/base.txt
|
||||
contextvars==2.4
|
||||
# via -r requirements/base.txt
|
||||
cryptography==41.0.5
|
||||
cryptography==41.0.7
|
||||
# via
|
||||
# -r requirements/base.txt
|
||||
# pyopenssl
|
||||
|
|
|
@ -24,7 +24,7 @@ clr-loader==0.2.4
|
|||
# via pythonnet
|
||||
contextvars==2.4
|
||||
# via -r requirements/base.txt
|
||||
cryptography==41.0.5
|
||||
cryptography==41.0.7
|
||||
# via
|
||||
# -r requirements/base.txt
|
||||
# pyopenssl
|
||||
|
|
|
@ -20,7 +20,7 @@ cherrypy==18.8.0
|
|||
# via -r requirements/base.txt
|
||||
contextvars==2.4
|
||||
# via -r requirements/base.txt
|
||||
cryptography==41.0.5
|
||||
cryptography==41.0.7
|
||||
# via
|
||||
# -r requirements/base.txt
|
||||
# pyopenssl
|
||||
|
|
|
@ -20,7 +20,7 @@ cherrypy==18.8.0
|
|||
# via -r requirements/base.txt
|
||||
contextvars==2.4
|
||||
# via -r requirements/base.txt
|
||||
cryptography==41.0.5
|
||||
cryptography==41.0.7
|
||||
# via
|
||||
# -r requirements/base.txt
|
||||
# pyopenssl
|
||||
|
|
|
@ -20,7 +20,7 @@ cherrypy==18.8.0
|
|||
# via -r requirements/base.txt
|
||||
contextvars==2.4
|
||||
# via -r requirements/base.txt
|
||||
cryptography==41.0.5
|
||||
cryptography==41.0.7
|
||||
# via
|
||||
# -r requirements/base.txt
|
||||
# pyopenssl
|
||||
|
|
|
@ -24,7 +24,7 @@ clr-loader==0.2.6
|
|||
# via pythonnet
|
||||
contextvars==2.4
|
||||
# via -r requirements/base.txt
|
||||
cryptography==41.0.5
|
||||
cryptography==41.0.7
|
||||
# via
|
||||
# -r requirements/base.txt
|
||||
# pyopenssl
|
||||
|
|
|
@ -18,7 +18,7 @@ cherrypy==18.8.0
|
|||
# via -r requirements/base.txt
|
||||
contextvars==2.4
|
||||
# via -r requirements/base.txt
|
||||
cryptography==41.0.5
|
||||
cryptography==41.0.7
|
||||
# via
|
||||
# -r requirements/base.txt
|
||||
# pyopenssl
|
||||
|
|
|
@ -18,7 +18,7 @@ cherrypy==18.8.0
|
|||
# via -r requirements/base.txt
|
||||
contextvars==2.4
|
||||
# via -r requirements/base.txt
|
||||
cryptography==41.0.5
|
||||
cryptography==41.0.7
|
||||
# via
|
||||
# -r requirements/base.txt
|
||||
# pyopenssl
|
||||
|
|
|
@ -22,7 +22,7 @@ clr-loader==0.2.4
|
|||
# via pythonnet
|
||||
contextvars==2.4
|
||||
# via -r requirements/base.txt
|
||||
cryptography==41.0.5
|
||||
cryptography==41.0.7
|
||||
# via
|
||||
# -r requirements/base.txt
|
||||
# pyopenssl
|
||||
|
|
|
@ -18,7 +18,7 @@ cherrypy==18.8.0
|
|||
# via -r requirements/base.txt
|
||||
contextvars==2.4
|
||||
# via -r requirements/base.txt
|
||||
cryptography==41.0.5
|
||||
cryptography==41.0.7
|
||||
# via
|
||||
# -r requirements/base.txt
|
||||
# pyopenssl
|
||||
|
|
|
@ -18,7 +18,7 @@ cherrypy==18.8.0
|
|||
# via -r requirements/base.txt
|
||||
contextvars==2.4
|
||||
# via -r requirements/base.txt
|
||||
cryptography==41.0.5
|
||||
cryptography==41.0.7
|
||||
# via
|
||||
# -r requirements/base.txt
|
||||
# pyopenssl
|
||||
|
|
|
@ -18,7 +18,7 @@ cherrypy==18.8.0
|
|||
# via -r requirements/base.txt
|
||||
contextvars==2.4
|
||||
# via -r requirements/base.txt
|
||||
cryptography==41.0.5
|
||||
cryptography==41.0.7
|
||||
# via
|
||||
# -r requirements/base.txt
|
||||
# pyopenssl
|
||||
|
|
|
@ -22,7 +22,7 @@ clr-loader==0.2.4
|
|||
# via pythonnet
|
||||
contextvars==2.4
|
||||
# via -r requirements/base.txt
|
||||
cryptography==41.0.5
|
||||
cryptography==41.0.7
|
||||
# via
|
||||
# -r requirements/base.txt
|
||||
# pyopenssl
|
||||
|
|
|
@ -22,6 +22,7 @@ import urllib.request
|
|||
import xml.etree.ElementTree as ET
|
||||
import zlib
|
||||
|
||||
import tornado.httpclient
|
||||
import tornado.httputil
|
||||
import tornado.simple_httpclient
|
||||
from tornado.httpclient import HTTPClient
|
||||
|
@ -37,6 +38,7 @@ import salt.utils.msgpack
|
|||
import salt.utils.network
|
||||
import salt.utils.platform
|
||||
import salt.utils.stringutils
|
||||
import salt.utils.url
|
||||
import salt.utils.xmlutil as xml
|
||||
import salt.utils.yaml
|
||||
import salt.version
|
||||
|
@ -62,14 +64,6 @@ except ImportError:
|
|||
HAS_MATCHHOSTNAME = False
|
||||
# pylint: enable=no-name-in-module
|
||||
|
||||
|
||||
try:
|
||||
import tornado.curl_httpclient
|
||||
|
||||
HAS_CURL_HTTPCLIENT = True
|
||||
except ImportError:
|
||||
HAS_CURL_HTTPCLIENT = False
|
||||
|
||||
try:
|
||||
import requests
|
||||
|
||||
|
@ -224,6 +218,37 @@ def query(
|
|||
if not backend:
|
||||
backend = opts.get("backend", "tornado")
|
||||
|
||||
proxy_host = opts.get("proxy_host", None)
|
||||
if proxy_host:
|
||||
proxy_host = salt.utils.stringutils.to_str(proxy_host)
|
||||
proxy_port = opts.get("proxy_port", None)
|
||||
proxy_username = opts.get("proxy_username", None)
|
||||
if proxy_username:
|
||||
proxy_username = salt.utils.stringutils.to_str(proxy_username)
|
||||
proxy_password = opts.get("proxy_password", None)
|
||||
if proxy_password:
|
||||
proxy_password = salt.utils.stringutils.to_str(proxy_password)
|
||||
no_proxy = opts.get("no_proxy", [])
|
||||
|
||||
if urllib.parse.urlparse(url).hostname in no_proxy:
|
||||
proxy_host = None
|
||||
proxy_port = None
|
||||
proxy_username = None
|
||||
proxy_password = None
|
||||
|
||||
http_proxy_url = None
|
||||
if proxy_host and proxy_port:
|
||||
if backend != "requests":
|
||||
log.debug("Switching to request backend due to the use of proxies.")
|
||||
backend = "requests"
|
||||
|
||||
if proxy_username and proxy_password:
|
||||
http_proxy_url = (
|
||||
f"http://{proxy_username}:{proxy_password}@{proxy_host}:{proxy_port}"
|
||||
)
|
||||
else:
|
||||
http_proxy_url = f"http://{proxy_host}:{proxy_port}"
|
||||
|
||||
match = re.match(
|
||||
r"https?://((25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(25[0-5]|2[0-4]\d|[01]?\d\d?)($|/)",
|
||||
url,
|
||||
|
@ -337,6 +362,8 @@ def query(
|
|||
log.trace("Request Headers: %s", sess.headers)
|
||||
sess_cookies = sess.cookies
|
||||
sess.verify = verify_ssl
|
||||
if http_proxy_url is not None:
|
||||
sess.proxies = {"http": http_proxy_url}
|
||||
elif backend == "urllib2":
|
||||
sess_cookies = None
|
||||
else:
|
||||
|
@ -555,52 +582,10 @@ def query(
|
|||
salt.config.DEFAULT_MINION_OPTS["http_request_timeout"],
|
||||
)
|
||||
|
||||
client_argspec = None
|
||||
|
||||
proxy_host = opts.get("proxy_host", None)
|
||||
if proxy_host:
|
||||
# tornado requires a str for proxy_host, cannot be a unicode str in py2
|
||||
proxy_host = salt.utils.stringutils.to_str(proxy_host)
|
||||
proxy_port = opts.get("proxy_port", None)
|
||||
proxy_username = opts.get("proxy_username", None)
|
||||
if proxy_username:
|
||||
# tornado requires a str, cannot be unicode str in py2
|
||||
proxy_username = salt.utils.stringutils.to_str(proxy_username)
|
||||
proxy_password = opts.get("proxy_password", None)
|
||||
if proxy_password:
|
||||
# tornado requires a str, cannot be unicode str in py2
|
||||
proxy_password = salt.utils.stringutils.to_str(proxy_password)
|
||||
no_proxy = opts.get("no_proxy", [])
|
||||
|
||||
# Since tornado doesnt support no_proxy, we'll always hand it empty proxies or valid ones
|
||||
# except we remove the valid ones if a url has a no_proxy hostname in it
|
||||
if urllib.parse.urlparse(url_full).hostname in no_proxy:
|
||||
proxy_host = None
|
||||
proxy_port = None
|
||||
proxy_username = None
|
||||
proxy_password = None
|
||||
|
||||
# We want to use curl_http if we have a proxy defined
|
||||
if proxy_host and proxy_port:
|
||||
if HAS_CURL_HTTPCLIENT is False:
|
||||
ret["error"] = (
|
||||
"proxy_host and proxy_port has been set. This requires pycurl and"
|
||||
" tornado, but the libraries does not seem to be installed"
|
||||
)
|
||||
log.error(ret["error"])
|
||||
return ret
|
||||
|
||||
tornado.httpclient.AsyncHTTPClient.configure(
|
||||
"tornado.curl_httpclient.CurlAsyncHTTPClient"
|
||||
)
|
||||
client_argspec = salt.utils.args.get_function_argspec(
|
||||
tornado.curl_httpclient.CurlAsyncHTTPClient.initialize
|
||||
)
|
||||
else:
|
||||
tornado.httpclient.AsyncHTTPClient.configure(None)
|
||||
client_argspec = salt.utils.args.get_function_argspec(
|
||||
tornado.simple_httpclient.SimpleAsyncHTTPClient.initialize
|
||||
)
|
||||
tornado.httpclient.AsyncHTTPClient.configure(None)
|
||||
client_argspec = salt.utils.args.get_function_argspec(
|
||||
tornado.simple_httpclient.SimpleAsyncHTTPClient.initialize
|
||||
)
|
||||
|
||||
supports_max_body_size = "max_body_size" in client_argspec.args
|
||||
|
||||
|
@ -617,10 +602,6 @@ def query(
|
|||
"header_callback": header_callback,
|
||||
"connect_timeout": connect_timeout,
|
||||
"request_timeout": timeout,
|
||||
"proxy_host": proxy_host,
|
||||
"proxy_port": proxy_port,
|
||||
"proxy_username": proxy_username,
|
||||
"proxy_password": proxy_password,
|
||||
"raise_error": raise_error,
|
||||
"decompress_response": False,
|
||||
}
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
import shutil
|
||||
import tarfile
|
||||
|
||||
import pytest
|
||||
from pytestshellutils.utils import ports
|
||||
from saltfactories.utils import random_string
|
||||
|
||||
import salt.utils.http
|
||||
|
||||
|
@ -14,3 +17,138 @@ def test_decode_body(webserver, integration_files_dir, backend):
|
|||
webserver.url("test.tar.gz"), backend=backend, decode_body=False
|
||||
)
|
||||
assert isinstance(ret["body"], bytes)
|
||||
|
||||
|
||||
pytestmark = [
|
||||
pytest.mark.slow_test,
|
||||
pytest.mark.skip_if_binaries_missing("docker", "dockerd", check_all=False),
|
||||
]
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def tinyproxy_port():
|
||||
return ports.get_unused_localhost_port()
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def tinyproxy_user():
|
||||
return random_string("tinyproxy-user-")
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def tinyproxy_pass():
|
||||
return random_string("tinyproxy-pass-")
|
||||
|
||||
|
||||
@pytest.fixture(params=[True, False], ids=lambda x: "basic-auth" if x else "no-auth")
|
||||
def tinyproxy_basic_auth(request):
|
||||
return request.param
|
||||
|
||||
|
||||
@pytest.fixture(params=[True, False], ids=lambda x: "no-proxy" if x else "with-proxy")
|
||||
def no_proxy(request):
|
||||
return request.param
|
||||
|
||||
|
||||
@pytest.fixture(params=["POST", "GET"], ids=lambda x: x)
|
||||
def http_method(request):
|
||||
return request.param
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def tinyproxy_dir(tmp_path_factory):
|
||||
try:
|
||||
dirname = tmp_path_factory.mktemp("tinyproxy")
|
||||
yield dirname
|
||||
finally:
|
||||
shutil.rmtree(dirname, ignore_errors=True)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def tinyproxy_conf(
|
||||
tinyproxy_dir, tinyproxy_port, tinyproxy_user, tinyproxy_pass, tinyproxy_basic_auth
|
||||
):
|
||||
basic_auth = (
|
||||
f"\nBasicAuth {tinyproxy_user} {tinyproxy_pass}" if tinyproxy_basic_auth else ""
|
||||
)
|
||||
conf = """Port {port}
|
||||
Listen 127.0.0.1
|
||||
Timeout 600
|
||||
Allow 127.0.0.1
|
||||
AddHeader "X-Tinyproxy-Header" "Test custom tinyproxy header"{auth}
|
||||
""".format(
|
||||
port=tinyproxy_port, auth=basic_auth
|
||||
)
|
||||
(tinyproxy_dir / "tinyproxy.conf").write_text(conf)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def tinyproxy_container(
|
||||
salt_factories,
|
||||
tinyproxy_conf,
|
||||
tinyproxy_dir,
|
||||
):
|
||||
container = salt_factories.get_container(
|
||||
"tinyproxy",
|
||||
image_name="ghcr.io/saltstack/salt-ci-containers/tinyproxy:latest",
|
||||
container_run_kwargs={
|
||||
"network_mode": "host",
|
||||
"volumes": {str(tinyproxy_dir): {"bind": "/etc/tinyproxy", "mode": "z"}},
|
||||
},
|
||||
pull_before_start=True,
|
||||
skip_on_pull_failure=True,
|
||||
skip_if_docker_client_not_connectable=True,
|
||||
)
|
||||
with container.started() as factory:
|
||||
yield factory
|
||||
|
||||
|
||||
@pytest.mark.parametrize("backend", ["requests", "tornado", "urllib2"])
|
||||
def test_real_proxy(
|
||||
tinyproxy_container,
|
||||
httpserver,
|
||||
tinyproxy_port,
|
||||
tinyproxy_user,
|
||||
tinyproxy_pass,
|
||||
backend,
|
||||
tinyproxy_basic_auth,
|
||||
no_proxy,
|
||||
http_method,
|
||||
):
|
||||
data = b"mydatahere"
|
||||
opts = {
|
||||
"proxy_host": "localhost",
|
||||
"proxy_port": tinyproxy_port,
|
||||
}
|
||||
if tinyproxy_basic_auth:
|
||||
opts.update(
|
||||
{
|
||||
"proxy_username": tinyproxy_user,
|
||||
"proxy_password": tinyproxy_pass,
|
||||
}
|
||||
)
|
||||
|
||||
# Expecting the headers allows verification that it went through the proxy without looking at the logs
|
||||
if no_proxy:
|
||||
opts["no_proxy"] = ["random.hostname.io", httpserver.host]
|
||||
httpserver.expect_request(
|
||||
"/real_proxy_test",
|
||||
).respond_with_data(data, content_type="application/octet-stream")
|
||||
else:
|
||||
httpserver.expect_request(
|
||||
"/real_proxy_test",
|
||||
headers={"X-Tinyproxy-Header": "Test custom tinyproxy header"},
|
||||
).respond_with_data(data, content_type="application/octet-stream")
|
||||
url = httpserver.url_for("/real_proxy_test").replace("localhost", "127.0.0.1")
|
||||
|
||||
# We just want to be sure that it's using the proxy
|
||||
ret = salt.utils.http.query(
|
||||
url,
|
||||
method=http_method,
|
||||
data=data,
|
||||
backend=backend,
|
||||
opts=opts,
|
||||
decode_body=False,
|
||||
)
|
||||
body = ret.get("body", "")
|
||||
assert body == data
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
import pytest
|
||||
import requests
|
||||
from pytestshellutils.utils import ports
|
||||
from werkzeug.wrappers import Response # pylint: disable=3rd-party-module-not-gated
|
||||
|
||||
import salt.utils.http
|
||||
import salt.utils.http as http
|
||||
from tests.support.mock import MagicMock, patch
|
||||
|
||||
|
||||
|
@ -16,10 +18,10 @@ def test_requests_session_verify_ssl_false(ssl_webserver, integration_files_dir)
|
|||
|
||||
if verify is True or verify is None:
|
||||
with pytest.raises(requests.exceptions.SSLError) as excinfo:
|
||||
session = salt.utils.http.session(**kwargs)
|
||||
session = http.session(**kwargs)
|
||||
ret = session.get(ssl_webserver.url("this.txt"))
|
||||
else:
|
||||
session = salt.utils.http.session(**kwargs)
|
||||
session = http.session(**kwargs)
|
||||
ret = session.get(ssl_webserver.url("this.txt"))
|
||||
assert ret.status_code == 200
|
||||
|
||||
|
@ -29,7 +31,7 @@ def test_session_ca_bundle_verify_false():
|
|||
test salt.utils.http.session when using
|
||||
both ca_bunlde and verify_ssl false
|
||||
"""
|
||||
ret = salt.utils.http.session(ca_bundle="/tmp/test_bundle", verify_ssl=False)
|
||||
ret = http.session(ca_bundle="/tmp/test_bundle", verify_ssl=False)
|
||||
assert ret is False
|
||||
|
||||
|
||||
|
@ -38,7 +40,7 @@ def test_session_headers():
|
|||
test salt.utils.http.session when setting
|
||||
headers
|
||||
"""
|
||||
ret = salt.utils.http.session(headers={"Content-Type": "application/json"})
|
||||
ret = http.session(headers={"Content-Type": "application/json"})
|
||||
assert ret.headers["Content-Type"] == "application/json"
|
||||
|
||||
|
||||
|
@ -49,5 +51,260 @@ def test_session_ca_bundle():
|
|||
fpath = "/tmp/test_bundle"
|
||||
patch_os = patch("os.path.exists", MagicMock(return_value=True))
|
||||
with patch_os:
|
||||
ret = salt.utils.http.session(ca_bundle=fpath)
|
||||
ret = http.session(ca_bundle=fpath)
|
||||
assert ret.verify == fpath
|
||||
|
||||
|
||||
def test_sanitize_url_hide_fields_none():
|
||||
"""
|
||||
Tests sanitizing a url when the hide_fields kwarg is None.
|
||||
"""
|
||||
mock_url = "https://api.testing.com/?&foo=bar&test=testing"
|
||||
ret = http.sanitize_url(mock_url, hide_fields=None)
|
||||
assert ret == mock_url
|
||||
|
||||
|
||||
def test_sanitize_url_no_elements():
|
||||
"""
|
||||
Tests sanitizing a url when no elements should be sanitized.
|
||||
"""
|
||||
mock_url = "https://api.testing.com/?&foo=bar&test=testing"
|
||||
ret = http.sanitize_url(mock_url, [""])
|
||||
assert ret == mock_url
|
||||
|
||||
|
||||
def test_sanitize_url_single_element():
|
||||
"""
|
||||
Tests sanitizing a url with only a single element to be sanitized.
|
||||
"""
|
||||
mock_url = (
|
||||
"https://api.testing.com/?&keep_it_secret=abcdefghijklmn"
|
||||
"&api_action=module.function"
|
||||
)
|
||||
mock_ret = (
|
||||
"https://api.testing.com/?&keep_it_secret=XXXXXXXXXX&"
|
||||
"api_action=module.function"
|
||||
)
|
||||
ret = http.sanitize_url(mock_url, ["keep_it_secret"])
|
||||
assert ret == mock_ret
|
||||
|
||||
|
||||
def test_sanitize_url_multiple_elements():
|
||||
"""
|
||||
Tests sanitizing a url with multiple elements to be sanitized.
|
||||
"""
|
||||
mock_url = (
|
||||
"https://api.testing.com/?rootPass=badpassword%21"
|
||||
"&skipChecks=True&api_key=abcdefghijklmn"
|
||||
"&NodeID=12345&api_action=module.function"
|
||||
)
|
||||
mock_ret = (
|
||||
"https://api.testing.com/?rootPass=XXXXXXXXXX"
|
||||
"&skipChecks=True&api_key=XXXXXXXXXX"
|
||||
"&NodeID=12345&api_action=module.function"
|
||||
)
|
||||
ret = http.sanitize_url(mock_url, ["api_key", "rootPass"])
|
||||
assert ret == mock_ret
|
||||
|
||||
|
||||
# _sanitize_components tests
|
||||
|
||||
|
||||
def test_sanitize_components_no_elements():
|
||||
"""
|
||||
Tests when zero elements need to be sanitized.
|
||||
"""
|
||||
mock_component_list = ["foo=bar", "bar=baz", "hello=world"]
|
||||
mock_ret = "foo=bar&bar=baz&hello=world&"
|
||||
ret = http._sanitize_url_components(mock_component_list, "api_key")
|
||||
assert ret == mock_ret
|
||||
|
||||
|
||||
def test_sanitize_components_one_element():
|
||||
"""
|
||||
Tests a single component to be sanitized.
|
||||
"""
|
||||
mock_component_list = ["foo=bar", "api_key=abcdefghijklmnop"]
|
||||
mock_ret = "foo=bar&api_key=XXXXXXXXXX&"
|
||||
ret = http._sanitize_url_components(mock_component_list, "api_key")
|
||||
assert ret == mock_ret
|
||||
|
||||
|
||||
def test_sanitize_components_multiple_elements():
|
||||
"""
|
||||
Tests two componenets to be sanitized.
|
||||
"""
|
||||
mock_component_list = ["foo=bar", "foo=baz", "api_key=testing"]
|
||||
mock_ret = "foo=XXXXXXXXXX&foo=XXXXXXXXXX&api_key=testing&"
|
||||
ret = http._sanitize_url_components(mock_component_list, "foo")
|
||||
assert ret == mock_ret
|
||||
|
||||
|
||||
@pytest.mark.slow_test
|
||||
def test_query_null_response():
|
||||
"""
|
||||
This tests that we get a null response when raise_error=False and the
|
||||
host/port cannot be reached.
|
||||
"""
|
||||
host = "127.0.0.1"
|
||||
|
||||
port = ports.get_unused_localhost_port()
|
||||
|
||||
url = "http://{host}:{port}/".format(host=host, port=port)
|
||||
result = http.query(url, raise_error=False)
|
||||
assert result == {"body": None}, result
|
||||
|
||||
|
||||
def test_query_error_handling():
|
||||
ret = http.query("http://127.0.0.1:0")
|
||||
assert isinstance(ret, dict)
|
||||
assert isinstance(ret.get("error", None), str)
|
||||
ret = http.query("http://myfoobardomainthatnotexist")
|
||||
assert isinstance(ret, dict)
|
||||
assert isinstance(ret.get("error", None), str)
|
||||
|
||||
|
||||
def test_parse_cookie_header():
|
||||
header = "; ".join(
|
||||
[
|
||||
"foo=bar",
|
||||
"expires=Mon, 03-Aug-20 14:26:27 GMT",
|
||||
"path=/",
|
||||
"domain=.mydomain.tld",
|
||||
"HttpOnly",
|
||||
"SameSite=Lax",
|
||||
"Secure",
|
||||
]
|
||||
)
|
||||
ret = http.parse_cookie_header(header)
|
||||
cookie = ret.pop(0)
|
||||
assert cookie.name == "foo", cookie.name
|
||||
assert cookie.value == "bar", cookie.value
|
||||
assert cookie.expires == 1596464787, cookie.expires
|
||||
assert cookie.path == "/", cookie.path
|
||||
assert cookie.domain == ".mydomain.tld", cookie.domain
|
||||
assert cookie.secure
|
||||
# Only one cookie should have been returned, if anything is left in the
|
||||
# parse_cookie_header return then something went wrong.
|
||||
assert not ret
|
||||
|
||||
|
||||
@pytest.mark.requires_network
|
||||
def test_requests_multipart_formdata_post(httpserver):
|
||||
"""
|
||||
Test handling of a multipart/form-data POST using the requests backend
|
||||
"""
|
||||
match_this = (
|
||||
"{0}\r\nContent-Disposition: form-data;"
|
||||
' name="fieldname_here"\r\n\r\nmydatahere\r\n{0}--\r\n'
|
||||
)
|
||||
|
||||
def mirror_post_handler(request):
|
||||
return Response(request.data)
|
||||
|
||||
httpserver.expect_request(
|
||||
"/multipart_form_data",
|
||||
).respond_with_handler(mirror_post_handler)
|
||||
url = httpserver.url_for("/multipart_form_data")
|
||||
|
||||
ret = http.query(
|
||||
url,
|
||||
method="POST",
|
||||
data="mydatahere",
|
||||
formdata=True,
|
||||
formdata_fieldname="fieldname_here",
|
||||
backend="requests",
|
||||
)
|
||||
body = ret.get("body", "")
|
||||
boundary = body[: body.find("\r")]
|
||||
assert body == match_this.format(boundary)
|
||||
|
||||
|
||||
def test_query_proxy(httpserver):
|
||||
"""
|
||||
Test http.query with tornado and with proxy opts set
|
||||
and then test with no_proxy set to ensure we dont
|
||||
run into issue #55192 again.
|
||||
"""
|
||||
data = "mydatahere"
|
||||
opts = {
|
||||
"proxy_host": "127.0.0.1",
|
||||
"proxy_port": 88,
|
||||
"proxy_username": "salt_test",
|
||||
"proxy_password": "super_secret",
|
||||
}
|
||||
|
||||
with patch("requests.Session") as mock_session:
|
||||
mock_session.return_value = MagicMock()
|
||||
ret = http.query(
|
||||
"https://fake_url",
|
||||
method="POST",
|
||||
data=data,
|
||||
backend="tornado",
|
||||
opts=opts,
|
||||
)
|
||||
|
||||
assert mock_session.return_value.proxies == {
|
||||
"http": "http://salt_test:super_secret@127.0.0.1:88"
|
||||
}
|
||||
|
||||
opts["no_proxy"] = [httpserver.host]
|
||||
|
||||
httpserver.expect_request(
|
||||
"/no_proxy_test",
|
||||
).respond_with_data(data)
|
||||
url = httpserver.url_for("/no_proxy_test")
|
||||
|
||||
with patch("requests.Session") as mock_session:
|
||||
mock_session.return_value = MagicMock()
|
||||
ret = http.query(
|
||||
url,
|
||||
method="POST",
|
||||
data=data,
|
||||
backend="tornado",
|
||||
opts=opts,
|
||||
)
|
||||
assert not isinstance(mock_session.return_value.proxies, dict)
|
||||
|
||||
ret = http.query(url, method="POST", data=data, backend="tornado", opts=opts)
|
||||
body = ret.get("body", "")
|
||||
assert body == data
|
||||
|
||||
|
||||
@pytest.mark.parametrize("backend", ["requests", "tornado", "urllib2"])
|
||||
def test_backends_decode_body_false(httpserver, backend):
|
||||
"""
|
||||
test all backends when using
|
||||
decode_body=False that it returns
|
||||
bytes and does not try to decode
|
||||
"""
|
||||
url = "/test-bytes"
|
||||
data = b"test-bytes"
|
||||
httpserver.expect_request(
|
||||
url,
|
||||
).respond_with_data(data, content_type="application/octet-stream")
|
||||
ret = http.query(
|
||||
httpserver.url_for(url),
|
||||
backend=backend,
|
||||
decode_body=False,
|
||||
)
|
||||
body = ret.get("body", "")
|
||||
assert isinstance(body, bytes)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("backend", ["requests", "tornado", "urllib2"])
|
||||
def test_backends_decode_body_true(httpserver, backend):
|
||||
"""
|
||||
test all backends when using decode_body=True that it returns string and decodes it.
|
||||
"""
|
||||
url = "/test-decoded-bytes"
|
||||
data = b"test-decoded-bytes"
|
||||
httpserver.expect_request(
|
||||
url,
|
||||
).respond_with_data(data, content_type="application/octet-stream")
|
||||
ret = http.query(
|
||||
httpserver.url_for(url),
|
||||
backend=backend,
|
||||
)
|
||||
body = ret.get("body", "")
|
||||
assert isinstance(body, str)
|
||||
|
|
|
@ -1,303 +0,0 @@
|
|||
"""
|
||||
:codeauthor: Nicole Thomas <nicole@saltstack.com>
|
||||
"""
|
||||
|
||||
import socket
|
||||
import sys
|
||||
from contextlib import closing
|
||||
|
||||
import pytest
|
||||
from saltfactories.utils.tempfiles import temp_file
|
||||
|
||||
import salt.utils.http as http
|
||||
from tests.support.helpers import MirrorPostHandler, Webserver
|
||||
from tests.support.mock import MagicMock, patch
|
||||
from tests.support.runtests import RUNTIME_VARS
|
||||
from tests.support.unit import TestCase
|
||||
|
||||
try:
|
||||
import tornado.curl_httpclient # pylint: disable=unused-import
|
||||
|
||||
HAS_CURL = True
|
||||
except ImportError:
|
||||
HAS_CURL = False
|
||||
|
||||
|
||||
class HTTPTestCase(TestCase):
|
||||
"""
|
||||
Unit TestCase for the salt.utils.http module.
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
cls.post_webserver = Webserver(handler=MirrorPostHandler)
|
||||
cls.post_webserver.start()
|
||||
cls.post_web_root = cls.post_webserver.web_root
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
cls.post_webserver.stop()
|
||||
del cls.post_webserver
|
||||
|
||||
# sanitize_url tests
|
||||
|
||||
def test_sanitize_url_hide_fields_none(self):
|
||||
"""
|
||||
Tests sanitizing a url when the hide_fields kwarg is None.
|
||||
"""
|
||||
mock_url = "https://api.testing.com/?&foo=bar&test=testing"
|
||||
ret = http.sanitize_url(mock_url, hide_fields=None)
|
||||
self.assertEqual(ret, mock_url)
|
||||
|
||||
def test_sanitize_url_no_elements(self):
|
||||
"""
|
||||
Tests sanitizing a url when no elements should be sanitized.
|
||||
"""
|
||||
mock_url = "https://api.testing.com/?&foo=bar&test=testing"
|
||||
ret = http.sanitize_url(mock_url, [""])
|
||||
self.assertEqual(ret, mock_url)
|
||||
|
||||
def test_sanitize_url_single_element(self):
|
||||
"""
|
||||
Tests sanitizing a url with only a single element to be sanitized.
|
||||
"""
|
||||
mock_url = (
|
||||
"https://api.testing.com/?&keep_it_secret=abcdefghijklmn"
|
||||
"&api_action=module.function"
|
||||
)
|
||||
mock_ret = (
|
||||
"https://api.testing.com/?&keep_it_secret=XXXXXXXXXX&"
|
||||
"api_action=module.function"
|
||||
)
|
||||
ret = http.sanitize_url(mock_url, ["keep_it_secret"])
|
||||
self.assertEqual(ret, mock_ret)
|
||||
|
||||
def test_sanitize_url_multiple_elements(self):
|
||||
"""
|
||||
Tests sanitizing a url with multiple elements to be sanitized.
|
||||
"""
|
||||
mock_url = (
|
||||
"https://api.testing.com/?rootPass=badpassword%21"
|
||||
"&skipChecks=True&api_key=abcdefghijklmn"
|
||||
"&NodeID=12345&api_action=module.function"
|
||||
)
|
||||
mock_ret = (
|
||||
"https://api.testing.com/?rootPass=XXXXXXXXXX"
|
||||
"&skipChecks=True&api_key=XXXXXXXXXX"
|
||||
"&NodeID=12345&api_action=module.function"
|
||||
)
|
||||
ret = http.sanitize_url(mock_url, ["api_key", "rootPass"])
|
||||
self.assertEqual(ret, mock_ret)
|
||||
|
||||
# _sanitize_components tests
|
||||
|
||||
def test_sanitize_components_no_elements(self):
|
||||
"""
|
||||
Tests when zero elements need to be sanitized.
|
||||
"""
|
||||
mock_component_list = ["foo=bar", "bar=baz", "hello=world"]
|
||||
mock_ret = "foo=bar&bar=baz&hello=world&"
|
||||
ret = http._sanitize_url_components(mock_component_list, "api_key")
|
||||
self.assertEqual(ret, mock_ret)
|
||||
|
||||
def test_sanitize_components_one_element(self):
|
||||
"""
|
||||
Tests a single component to be sanitized.
|
||||
"""
|
||||
mock_component_list = ["foo=bar", "api_key=abcdefghijklmnop"]
|
||||
mock_ret = "foo=bar&api_key=XXXXXXXXXX&"
|
||||
ret = http._sanitize_url_components(mock_component_list, "api_key")
|
||||
self.assertEqual(ret, mock_ret)
|
||||
|
||||
def test_sanitize_components_multiple_elements(self):
|
||||
"""
|
||||
Tests two componenets to be sanitized.
|
||||
"""
|
||||
mock_component_list = ["foo=bar", "foo=baz", "api_key=testing"]
|
||||
mock_ret = "foo=XXXXXXXXXX&foo=XXXXXXXXXX&api_key=testing&"
|
||||
ret = http._sanitize_url_components(mock_component_list, "foo")
|
||||
self.assertEqual(ret, mock_ret)
|
||||
|
||||
@pytest.mark.slow_test
|
||||
def test_query_null_response(self):
|
||||
"""
|
||||
This tests that we get a null response when raise_error=False and the
|
||||
host/port cannot be reached.
|
||||
"""
|
||||
host = "127.0.0.1"
|
||||
|
||||
# Find unused port
|
||||
with closing(socket.socket(socket.AF_INET, socket.SOCK_STREAM)) as sock:
|
||||
sock.bind((host, 0))
|
||||
port = sock.getsockname()[1]
|
||||
|
||||
url = "http://{host}:{port}/".format(host=host, port=port)
|
||||
result = http.query(url, raise_error=False)
|
||||
if sys.platform.startswith("win"):
|
||||
assert result == {"error": "[Errno 10061] Unknown error"}, result
|
||||
else:
|
||||
assert result == {"error": "[Errno 111] Connection refused"}, result
|
||||
|
||||
def test_query_error_handling(self):
|
||||
ret = http.query("http://127.0.0.1:0")
|
||||
self.assertTrue(isinstance(ret, dict))
|
||||
self.assertTrue(isinstance(ret.get("error", None), str))
|
||||
ret = http.query("http://myfoobardomainthatnotexist")
|
||||
self.assertTrue(isinstance(ret, dict))
|
||||
self.assertTrue(isinstance(ret.get("error", None), str))
|
||||
|
||||
def test_parse_cookie_header(self):
|
||||
header = "; ".join(
|
||||
[
|
||||
"foo=bar",
|
||||
"expires=Mon, 03-Aug-20 14:26:27 GMT",
|
||||
"path=/",
|
||||
"domain=.mydomain.tld",
|
||||
"HttpOnly",
|
||||
"SameSite=Lax",
|
||||
"Secure",
|
||||
]
|
||||
)
|
||||
ret = http.parse_cookie_header(header)
|
||||
cookie = ret.pop(0)
|
||||
assert cookie.name == "foo", cookie.name
|
||||
assert cookie.value == "bar", cookie.value
|
||||
assert cookie.expires == 1596464787, cookie.expires
|
||||
assert cookie.path == "/", cookie.path
|
||||
assert cookie.domain == ".mydomain.tld", cookie.domain
|
||||
assert cookie.secure
|
||||
# Only one cookie should have been returned, if anything is left in the
|
||||
# parse_cookie_header return then something went wrong.
|
||||
assert not ret
|
||||
|
||||
|
||||
class HTTPPostTestCase(TestCase):
|
||||
"""
|
||||
Unit TestCase for the salt.utils.http module when
|
||||
using POST method
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
cls.post_webserver = Webserver(handler=MirrorPostHandler)
|
||||
cls.post_webserver.start()
|
||||
cls.post_web_root = cls.post_webserver.web_root
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
cls.post_webserver.stop()
|
||||
del cls.post_webserver
|
||||
|
||||
def test_requests_multipart_formdata_post(self):
|
||||
"""
|
||||
Test handling of a multipart/form-data POST using the requests backend
|
||||
"""
|
||||
match_this = (
|
||||
"{0}\r\nContent-Disposition: form-data;"
|
||||
' name="fieldname_here"\r\n\r\nmydatahere\r\n{0}--\r\n'
|
||||
)
|
||||
ret = http.query(
|
||||
self.post_web_root,
|
||||
method="POST",
|
||||
data="mydatahere",
|
||||
formdata=True,
|
||||
formdata_fieldname="fieldname_here",
|
||||
backend="requests",
|
||||
)
|
||||
body = ret.get("body", "")
|
||||
boundary = body[: body.find("\r")]
|
||||
self.assertEqual(body, match_this.format(boundary))
|
||||
|
||||
@pytest.mark.skipif(
|
||||
HAS_CURL is False,
|
||||
reason="Missing prerequisites for tornado.curl_httpclient library",
|
||||
)
|
||||
def test_query_proxy(self):
|
||||
"""
|
||||
Test http.query with tornado and with proxy opts set
|
||||
and then test with no_proxy set to ensure we dont
|
||||
run into issue #55192 again.
|
||||
"""
|
||||
data = "mydatahere"
|
||||
opts = {
|
||||
"proxy_host": "127.0.0.1",
|
||||
"proxy_port": 88,
|
||||
"proxy_username": "salt_test",
|
||||
"proxy_password": "super_secret",
|
||||
}
|
||||
|
||||
mock_curl = MagicMock()
|
||||
|
||||
with patch("tornado.httpclient.HTTPClient.fetch", mock_curl):
|
||||
ret = http.query(
|
||||
self.post_web_root,
|
||||
method="POST",
|
||||
data=data,
|
||||
backend="tornado",
|
||||
opts=opts,
|
||||
)
|
||||
|
||||
for opt in opts:
|
||||
assert opt in mock_curl.call_args_list[0][1].keys()
|
||||
|
||||
opts["no_proxy"] = ["127.0.0.1"]
|
||||
|
||||
ret = http.query(
|
||||
self.post_web_root, method="POST", data=data, backend="tornado", opts=opts
|
||||
)
|
||||
body = ret.get("body", "")
|
||||
assert body == data
|
||||
|
||||
|
||||
class HTTPGetTestCase(TestCase):
|
||||
"""
|
||||
Unit TestCase for the salt.utils.http module when
|
||||
using Get method
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
cls.get_webserver = Webserver()
|
||||
cls.get_webserver.start()
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
cls.get_webserver.stop()
|
||||
del cls.get_webserver
|
||||
|
||||
def test_backends_decode_body_false(self):
|
||||
"""
|
||||
test all backends when using
|
||||
decode_body=False that it returns
|
||||
bytes and does not try to decode
|
||||
"""
|
||||
for backend in ["tornado", "requests", "urllib2"]:
|
||||
ret = http.query(
|
||||
self.get_webserver.url("custom.tar.gz"),
|
||||
backend=backend,
|
||||
decode_body=False,
|
||||
)
|
||||
body = ret.get("body", "")
|
||||
assert isinstance(body, bytes)
|
||||
|
||||
def test_backends_decode_body_true(self):
|
||||
"""
|
||||
test all backends when using
|
||||
decode_body=True that it returns
|
||||
string and decodes it.
|
||||
"""
|
||||
core_state = """
|
||||
{}:
|
||||
file:
|
||||
- managed
|
||||
- source: salt://testfile
|
||||
- makedirs: true
|
||||
""".format(
|
||||
RUNTIME_VARS.TMP
|
||||
)
|
||||
|
||||
with temp_file("core.sls", core_state, self.get_webserver.root):
|
||||
for backend in ["tornado", "requests", "urllib2"]:
|
||||
ret = http.query(self.get_webserver.url("core.sls"), backend=backend)
|
||||
body = ret.get("body", "")
|
||||
assert isinstance(body, str)
|
Loading…
Add table
Reference in a new issue