Skip to content

Commit 0a03a6b

Browse files
SG-38306 Remove Python 2 - Part 10 - SSL (#372)
* SSL cleanups --------- Co-authored-by: Eduardo Chauca <[email protected]>
1 parent f7d076b commit 0a03a6b

File tree

5 files changed

+21
-184
lines changed

5 files changed

+21
-184
lines changed

docs/advanced/iron_python.rst

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,12 @@ that we will be compatible with future releases of IronPython. While we don't of
77
IronPython, we certainly will do our best to figure out any issues that come up while using it and
88
how to avoid them.
99

10+
11+
Legacy Information
12+
------------------
13+
14+
This following information is provided for historical purposes only.
15+
1016
As of July 9, 2015 you can look at this fork of the repo to see what changes were needed as of that
1117
date to make things work. The original fork was as of v3.0.20 of the API. Big thanks to our awesome
1218
clients Pixomondo for making their work public and letting us refer to it:
@@ -20,12 +26,6 @@ v3.0.20 can be used with IronPython with a little bit of added work:
2026
https://bitbucket.org/jdhardy/ironpythonzlib/src/. And the blog post about it here
2127
http://blog.jdhardy.ca/2008/12/solving-zlib-problem-ironpythonzlib.html
2228

23-
- If you encounter any SSL errors like
24-
``unknown field: SERIALNUMBER=0123456789`` or ``:SSL3_GET_SERVER_CERTIFICATE:certificate verify failed``.
25-
For now you can workaround this problem by disabling ssl certificate validation which we've
26-
encountered some intermittent issues with. Set ``NO_SSL_VALIDATION = True`` for either case.
27-
See :const:`shotgun_api3.shotgun.NO_SSL_VALIDATION`
28-
2929
- If you encounter ``LookupError: unknown encoding: idna``, you can force utf-8 by changing
3030
iri2uri.py ~ln 71 from ``authority = authority.encode('idna')`` to
3131
``authority = authority.encode('utf-8')``

docs/reference.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ The :mod:`~shotgun_api3.shotgun` module is a container for the :class:`~shotgun.
1515
class. There are a couple of useful attributes to note.
1616

1717
.. automodule:: shotgun_api3.shotgun
18-
:members: NO_SSL_VALIDATION, LOG
18+
:members: LOG
1919
:private-members:
2020
:special-members:
2121

shotgun_api3/shotgun.py

Lines changed: 11 additions & 100 deletions
Original file line numberDiff line numberDiff line change
@@ -78,14 +78,6 @@
7878

7979
SHOTGUN_API_DISABLE_ENTITY_OPTIMIZATION = False
8080

81-
NO_SSL_VALIDATION = False
82-
"""
83-
Turns off hostname matching validation for SSL certificates
84-
85-
Sometimes there are cases where certificate validation should be disabled. For example, if you
86-
have a self-signed internal certificate that isn't included in our certificate bundle, you may
87-
not require the added security provided by enforcing this.
88-
"""
8981

9082
# ----------------------------------------------------------------------------
9183
# Version
@@ -327,9 +319,7 @@ class ClientCapabilities(object):
327319
:ivar str local_path_field: The PTR field used for local file paths. This is calculated using
328320
the value of ``platform``. Ex. ``local_path_mac``.
329321
:ivar str py_version: Simple version of Python executable as a string. Eg. ``3.9``.
330-
:ivar str ssl_version: Version of OpenSSL installed. Eg. ``OpenSSL 1.0.2g 1 Mar 2016``. This
331-
info is only available in Python 2.7+ if the ssl module was imported successfully.
332-
Defaults to ``unknown``
322+
:ivar str ssl_version: Version of OpenSSL installed. Eg. ``OpenSSL 1.0.2g 1 Mar 2016``.
333323
"""
334324

335325
def __init__(self):
@@ -350,14 +340,7 @@ def __init__(self):
350340
self.local_path_field = None
351341

352342
self.py_version = ".".join(str(x) for x in sys.version_info[:2])
353-
354-
# extract the OpenSSL version if we can. The version is only available in Python 2.7 and
355-
# only if we successfully imported ssl
356-
self.ssl_version = "unknown"
357-
try:
358-
self.ssl_version = ssl.OPENSSL_VERSION
359-
except (AttributeError, NameError):
360-
pass
343+
self.ssl_version = ssl.OPENSSL_VERSION
361344

362345
def __str__(self):
363346
return (
@@ -425,7 +408,6 @@ def __init__(self, sg):
425408
self.proxy_pass = None
426409
self.session_token = None
427410
self.authorization = None
428-
self.no_ssl_validation = False
429411
self.localized = False
430412

431413
def set_server_params(self, base_url):
@@ -616,7 +598,6 @@ def __init__(
616598
self.config.session_token = session_token
617599
self.config.sudo_as_login = sudo_as_login
618600
self.config.convert_datetimes_to_utc = convert_datetimes_to_utc
619-
self.config.no_ssl_validation = NO_SSL_VALIDATION
620601
self.config.raw_http_proxy = http_proxy
621602

622603
try:
@@ -2264,14 +2245,10 @@ def reset_user_agent(self):
22642245
ua_platform = self.client_caps.platform.capitalize()
22652246

22662247
# create ssl validation string based on settings
2267-
validation_str = "validate"
2268-
if self.config.no_ssl_validation:
2269-
validation_str = "no-validate"
2270-
22712248
self._user_agents = [
22722249
"shotgun-json (%s)" % __version__,
22732250
"Python %s (%s)" % (self.client_caps.py_version, ua_platform),
2274-
"ssl %s (%s)" % (self.client_caps.ssl_version, validation_str),
2251+
"ssl %s" % (self.client_caps.ssl_version),
22752252
]
22762253

22772254
def set_session_uuid(self, session_uuid):
@@ -3543,8 +3520,14 @@ def _build_opener(self, handler):
35433520
Build urllib2 opener with appropriate proxy handler.
35443521
"""
35453522
handlers = []
3546-
if self.__ca_certs and not NO_SSL_VALIDATION:
3547-
handlers.append(CACertsHTTPSHandler(self.__ca_certs))
3523+
if self.__ca_certs:
3524+
handlers.append(
3525+
urllib.request.HTTPSHandler(
3526+
context=ssl.create_default_context(
3527+
cafile=self.__ca_certs,
3528+
),
3529+
),
3530+
)
35483531

35493532
if self.config.proxy_handler:
35503533
handlers.append(self.config.proxy_handler)
@@ -3613,23 +3596,6 @@ def _get_certs_file(cls, ca_certs):
36133596
cert_file = os.path.join(cur_dir, "lib", "certifi", "cacert.pem")
36143597
return cert_file
36153598

3616-
def _turn_off_ssl_validation(self):
3617-
"""
3618-
Turn off SSL certificate validation.
3619-
"""
3620-
global NO_SSL_VALIDATION
3621-
self.config.no_ssl_validation = True
3622-
NO_SSL_VALIDATION = True
3623-
# reset ssl-validation in user-agents
3624-
self._user_agents = [
3625-
(
3626-
"ssl %s (no-validate)" % self.client_caps.ssl_version
3627-
if ua.startswith("ssl ")
3628-
else ua
3629-
)
3630-
for ua in self._user_agents
3631-
]
3632-
36333599
# Deprecated methods from old wrapper
36343600
def schema(self, entity_type):
36353601
"""
@@ -3843,44 +3809,7 @@ def _make_call(self, verb, path, body, headers):
38433809
if attempt == max_rpc_attempts:
38443810
LOG.debug("Request failed. Giving up after %d attempts." % attempt)
38453811
raise
3846-
# This is the exact same block as the "except Exception" bellow.
3847-
# We need to do it here because the next except will match it
3848-
# otherwise and will not re-attempt.
3849-
# When we drop support of Python 2 and we will probably drop the
3850-
# next except, we might want to remove this except too.
38513812
except (ssl.SSLError, ssl.CertificateError) as e:
3852-
# Test whether the exception is due to the fact that this is an older version of
3853-
# Python that cannot validate certificates encrypted with SHA-2. If it is, then
3854-
# fall back on disabling the certificate validation and try again - unless the
3855-
# SHOTGUN_FORCE_CERTIFICATE_VALIDATION environment variable has been set by the
3856-
# user. In that case we simply raise the exception. Any other exceptions simply
3857-
# get raised as well.
3858-
#
3859-
# For more info see:
3860-
# https://www.shotgridsoftware.com/blog/important-ssl-certificate-renewal-and-sha-2/
3861-
#
3862-
# SHA-2 errors look like this:
3863-
# [Errno 1] _ssl.c:480: error:0D0C50A1:asn1 encoding routines:ASN1_item_verify:
3864-
# unknown message digest algorithm
3865-
#
3866-
# Any other exceptions simply get raised.
3867-
if (
3868-
"unknown message digest algorithm" not in str(e)
3869-
or "SHOTGUN_FORCE_CERTIFICATE_VALIDATION" in os.environ
3870-
):
3871-
raise
3872-
3873-
if self.config.no_ssl_validation is False:
3874-
LOG.warning(
3875-
"SSL Error: this Python installation is incompatible with "
3876-
"certificates signed with SHA-2. Disabling certificate validation. "
3877-
"For more information, see https://www.shotgridsoftware.com/blog/"
3878-
"important-ssl-certificate-renewal-and-sha-2/"
3879-
)
3880-
self._turn_off_ssl_validation()
3881-
# reload user agent to reflect that we have turned off ssl validation
3882-
req_headers["user-agent"] = "; ".join(self._user_agents)
3883-
38843813
self._close_connection()
38853814
if attempt == max_rpc_attempts:
38863815
LOG.debug("Request failed. Giving up after %d attempts." % attempt)
@@ -4142,14 +4071,12 @@ def _get_connection(self):
41424071
timeout=self.config.timeout_secs,
41434072
ca_certs=self.__ca_certs,
41444073
proxy_info=pi,
4145-
disable_ssl_certificate_validation=self.config.no_ssl_validation,
41464074
)
41474075
else:
41484076
self._connection = Http(
41494077
timeout=self.config.timeout_secs,
41504078
ca_certs=self.__ca_certs,
41514079
proxy_info=None,
4152-
disable_ssl_certificate_validation=self.config.no_ssl_validation,
41534080
)
41544081

41554082
return self._connection
@@ -4613,22 +4540,6 @@ def connect(self):
46134540
)
46144541

46154542

4616-
class CACertsHTTPSHandler(urllib.request.HTTPHandler):
4617-
"""
4618-
Handler that ensures https connections are created with the custom CA certs.
4619-
"""
4620-
4621-
def __init__(self, cacerts):
4622-
super().__init__(self)
4623-
self.__ca_certs = cacerts
4624-
4625-
def https_open(self, req):
4626-
return self.do_open(self.create_https_connection, req)
4627-
4628-
def create_https_connection(self, *args, **kwargs):
4629-
return CACertsHTTPSConnection(*args, ca_certs=self.__ca_certs, **kwargs)
4630-
4631-
46324543
# Helpers from the previous API, left as is.
46334544
# Based on http://code.activestate.com/recipes/146306/
46344545
class FormPostHandler(urllib.request.BaseHandler):

tests/test_api.py

Lines changed: 0 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -2214,77 +2214,6 @@ def my_side_effect2(*args, **kwargs):
22142214
finally:
22152215
self.sg.config.rpc_attempt_interval = bak_rpc_attempt_interval
22162216

2217-
@unittest.mock.patch("shotgun_api3.shotgun.Http.request")
2218-
def test_sha2_error(self, mock_request):
2219-
# Simulate the exception raised with SHA-2 errors
2220-
mock_request.side_effect = ssl.SSLError(
2221-
"[Errno 1] _ssl.c:480: error:0D0C50A1:asn1 "
2222-
"encoding routines:ASN1_item_verify: unknown message digest "
2223-
"algorithm"
2224-
)
2225-
2226-
# save the original state
2227-
original_env_val = os.environ.pop("SHOTGUN_FORCE_CERTIFICATE_VALIDATION", None)
2228-
2229-
# ensure we're starting with the right values
2230-
self.sg.reset_user_agent()
2231-
2232-
# ensure the initial settings are correct. These will be different depending on whether
2233-
# the ssl module imported successfully or not.
2234-
if "ssl" in sys.modules:
2235-
self.assertFalse(self.sg.config.no_ssl_validation)
2236-
self.assertFalse(shotgun_api3.shotgun.NO_SSL_VALIDATION)
2237-
self.assertTrue("(validate)" in " ".join(self.sg._user_agents))
2238-
self.assertFalse("(no-validate)" in " ".join(self.sg._user_agents))
2239-
else:
2240-
self.assertTrue(self.sg.config.no_ssl_validation)
2241-
self.assertTrue(shotgun_api3.shotgun.NO_SSL_VALIDATION)
2242-
self.assertFalse("(validate)" in " ".join(self.sg._user_agents))
2243-
self.assertTrue("(no-validate)" in " ".join(self.sg._user_agents))
2244-
2245-
try:
2246-
self.sg.info()
2247-
except ssl.SSLError:
2248-
# ensure the api has reset the values in the correct fallback behavior
2249-
self.assertTrue(self.sg.config.no_ssl_validation)
2250-
self.assertTrue(shotgun_api3.shotgun.NO_SSL_VALIDATION)
2251-
self.assertFalse("(validate)" in " ".join(self.sg._user_agents))
2252-
self.assertTrue("(no-validate)" in " ".join(self.sg._user_agents))
2253-
2254-
if original_env_val is not None:
2255-
os.environ["SHOTGUN_FORCE_CERTIFICATE_VALIDATION"] = original_env_val
2256-
2257-
@unittest.mock.patch("shotgun_api3.shotgun.Http.request")
2258-
def test_sha2_error_with_strict(self, mock_request):
2259-
# Simulate the exception raised with SHA-2 errors
2260-
mock_request.side_effect = ssl.SSLError(
2261-
"[Errno 1] _ssl.c:480: error:0D0C50A1:asn1 "
2262-
"encoding routines:ASN1_item_verify: unknown message digest "
2263-
"algorithm"
2264-
)
2265-
2266-
# save the original state
2267-
original_env_val = os.environ.pop("SHOTGUN_FORCE_CERTIFICATE_VALIDATION", None)
2268-
os.environ["SHOTGUN_FORCE_CERTIFICATE_VALIDATION"] = "1"
2269-
2270-
# ensure we're starting with the right values
2271-
self.sg.config.no_ssl_validation = False
2272-
shotgun_api3.shotgun.NO_SSL_VALIDATION = False
2273-
self.sg.reset_user_agent()
2274-
2275-
try:
2276-
self.sg.info()
2277-
except ssl.SSLError:
2278-
# ensure the api has NOT reset the values in the fallback behavior because we have
2279-
# set the env variable to force validation
2280-
self.assertFalse(self.sg.config.no_ssl_validation)
2281-
self.assertFalse(shotgun_api3.shotgun.NO_SSL_VALIDATION)
2282-
self.assertFalse("(no-validate)" in " ".join(self.sg._user_agents))
2283-
self.assertTrue("(validate)" in " ".join(self.sg._user_agents))
2284-
2285-
if original_env_val is not None:
2286-
os.environ["SHOTGUN_FORCE_CERTIFICATE_VALIDATION"] = original_env_val
2287-
22882217
@unittest.mock.patch.object(urllib.request.OpenerDirector, "open")
22892218
def test_sanitized_auth_params(self, mock_open):
22902219
# Simulate the server blowing up and giving us a 500 error

tests/test_client.py

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -269,12 +269,11 @@ def test_user_agent(self):
269269
args, _ = self.sg._http_request.call_args
270270
(_, _, _, headers) = args
271271
ssl_validate_lut = {True: "no-validate", False: "validate"}
272-
expected = "shotgun-json (%s); Python %s (%s); ssl %s (%s)" % (
272+
expected = "shotgun-json (%s); Python %s (%s); ssl %s" % (
273273
api.__version__,
274274
client_caps.py_version,
275275
client_caps.platform.capitalize(),
276276
client_caps.ssl_version,
277-
ssl_validate_lut[config.no_ssl_validation],
278277
)
279278
self.assertEqual(expected, headers.get("user-agent"))
280279

@@ -283,12 +282,11 @@ def test_user_agent(self):
283282
self.sg.info()
284283
args, _ = self.sg._http_request.call_args
285284
(_, _, _, headers) = args
286-
expected = "shotgun-json (%s); Python %s (%s); ssl %s (%s); test-agent" % (
285+
expected = "shotgun-json (%s); Python %s (%s); ssl %s; test-agent" % (
287286
api.__version__,
288287
client_caps.py_version,
289288
client_caps.platform.capitalize(),
290289
client_caps.ssl_version,
291-
ssl_validate_lut[config.no_ssl_validation],
292290
)
293291
self.assertEqual(expected, headers.get("user-agent"))
294292

@@ -297,12 +295,11 @@ def test_user_agent(self):
297295
self.sg.info()
298296
args, _ = self.sg._http_request.call_args
299297
(_, _, _, headers) = args
300-
expected = "shotgun-json (%s); Python %s (%s); ssl %s (%s)" % (
298+
expected = "shotgun-json (%s); Python %s (%s); ssl %s" % (
301299
api.__version__,
302300
client_caps.py_version,
303301
client_caps.platform.capitalize(),
304302
client_caps.ssl_version,
305-
ssl_validate_lut[config.no_ssl_validation],
306303
)
307304
self.assertEqual(expected, headers.get("user-agent"))
308305

0 commit comments

Comments
 (0)