From 2b951445a8bd2c4a2b7ff965fd7ead175e5ed901 Mon Sep 17 00:00:00 2001 From: Andy Gearhart Date: Tue, 28 Nov 2023 08:56:45 -0800 Subject: [PATCH] feat: Allow for not verifying SSL certificates for requests Many corporate networks use SSL decryption of requests for security purposes akin to a man in the middle attack. This decryption causes underlying frameworks to throw SSL exceptions. It would be nice to provide an option to allow users to disable certificate verification of requests. --- FuelSDK/client.py | 23 +++++++++++++++++------ FuelSDK/rest.py | 16 ++++++++-------- FuelSDK/unverified_https_transport.py | 19 +++++++++++++++++++ README.md | 8 +++++++- 4 files changed, 51 insertions(+), 15 deletions(-) create mode 100644 FuelSDK/unverified_https_transport.py diff --git a/FuelSDK/client.py b/FuelSDK/client.py index 1ebbfb8..6f42bc4 100644 --- a/FuelSDK/client.py +++ b/FuelSDK/client.py @@ -10,6 +10,8 @@ import suds.wsse from suds.sax.element import Element +from unverified_https_transport import UnverifiedHttpsTransport + from FuelSDK.objects import ET_DataExtension,ET_Subscriber @@ -40,6 +42,7 @@ class ET_Client(object): application_type = None authorization_code = None redirect_URI = None + ssl_verify = True ## get_server_wsdl - if True and a newer WSDL is on the server than the local filesystem retrieve it def __init__(self, get_server_wsdl = False, debug = False, params = None, tokenResponse=None): @@ -180,6 +183,13 @@ def configure_client(self, get_server_wsdl, params, tokenResponse): elif "FUELSDK_REDIRECT_URI" in os.environ: self.redirect_URI = os.environ["FUELSDK_REDIRECT_URI"] + if params is not None and "ssl_verify" in params: + self.ssl_verify = params["ssl_verify"] + elif config.has_option("Auth Service", "ssl_verify"): + self.ssl_verify = config.getboolean("Auth Service", "ssl_verify") + elif config.has_option("Web Services", "ssl_verify"): + self.ssl_verify = config.getboolean("Web Services", "ssl_verify") + if self.application_type in ["public", "web"]: if self.is_none_or_empty_or_blank(self.authorization_code) or self.is_none_or_empty_or_blank(self.redirect_URI): raise Exception('authorizationCode or redirectURI is null: For Public/Web Apps, the authorizationCode and redirectURI must be ' @@ -223,7 +233,7 @@ def load_wsdl(self, wsdl_url, wsdl_file_local_location, get_server_wsdl = False) if not os.path.exists(file_location) or os.path.getsize(file_location) == 0: #if there is no local copy or local copy is empty then go get it... self.retrieve_server_wsdl(wsdl_url, file_location) elif get_server_wsdl: - r = requests.head(wsdl_url) + r = requests.head(wsdl_url, verify=self.ssl_verify) if r is not None and 'last-modified' in r.headers: server_wsdl_updated = time.strptime(r.headers['last-modified'], '%a, %d %b %Y %H:%M:%S %Z') file_wsdl_updated = time.gmtime(os.path.getmtime(file_location)) @@ -237,7 +247,7 @@ def retrieve_server_wsdl(self, wsdl_url, file_location): """ get the WSDL from the server and save it locally """ - r = requests.get(wsdl_url) + r = requests.get(wsdl_url, verify=self.ssl_verify) f = open(file_location, 'w') f.write(r.text) @@ -246,7 +256,8 @@ def build_soap_client(self): if self.soap_endpoint is None or not self.soap_endpoint: self.soap_endpoint = self.get_soap_endpoint() - self.soap_client = suds.client.Client(self.wsdl_file_url, faults=False, cachingpolicy=1) + https_transport = None if self.ssl_verify else UnverifiedHttpsTransport() + self.soap_client = suds.client.Client(self.wsdl_file_url, faults=False, cachingpolicy=1, https_transport=https_transport) self.soap_client.set_options(location=self.soap_endpoint) self.soap_client.set_options(headers={'user-agent' : 'FuelSDK-Python-v1.3.0'}) @@ -290,7 +301,7 @@ def refresh_token(self, force_refresh = False): self.auth_url = self.auth_url.strip() self.auth_url = self.auth_url + legacyString - r = requests.post(self.auth_url, data=json.dumps(payload), headers=headers) + r = requests.post(self.auth_url, data=json.dumps(payload), headers=headers, verify=self.ssl_verify) tokenResponse = r.json() if 'accessToken' not in tokenResponse: @@ -319,7 +330,7 @@ def refresh_token_with_oAuth2(self, force_refresh=False): auth_endpoint = self.auth_url.strip() + '/v2/token' - r = requests.post(auth_endpoint, data=json.dumps(payload), headers=headers) + r = requests.post(auth_endpoint, data=json.dumps(payload), headers=headers, verify=self.ssl_verify) tokenResponse = r.json() if 'access_token' not in tokenResponse: @@ -393,7 +404,7 @@ def get_soap_endpoint(self): r = requests.get(self.base_api_url + '/platform/v1/endpoints/soap', headers={ 'user-agent': 'FuelSDK-Python-v1.3.0', 'authorization': 'Bearer ' + self.authToken - }) + }, verify=self.ssl_verify) contextResponse = r.json() if ('url' in contextResponse): diff --git a/FuelSDK/rest.py b/FuelSDK/rest.py index 698447b..a1e066b 100644 --- a/FuelSDK/rest.py +++ b/FuelSDK/rest.py @@ -323,7 +323,7 @@ def getMoreResults(self): ## ######## class ET_GetRest(ET_Constructor): - def __init__(self, auth_stub, endpoint, qs = None): + def __init__(self, auth_stub, endpoint, qs = None, ssl_verify = True): auth_stub.refresh_token() fullendpoint = endpoint urlSeparator = '?' @@ -332,7 +332,7 @@ def __init__(self, auth_stub, endpoint, qs = None): urlSeparator = '&' headers = {'authorization' : 'Bearer ' + auth_stub.authToken, 'user-agent' : 'FuelSDK-Python-v1.3.0'} - r = requests.get(fullendpoint, headers=headers) + r = requests.get(fullendpoint, headers=headers, verify=ssl_verify) self.more_results = False @@ -346,11 +346,11 @@ def __init__(self, auth_stub, endpoint, qs = None): ## ######## class ET_PostRest(ET_Constructor): - def __init__(self, auth_stub, endpoint, payload): + def __init__(self, auth_stub, endpoint, payload, ssl_verify = True): auth_stub.refresh_token() headers = {'content-type' : 'application/json', 'user-agent' : 'FuelSDK-Python-v1.3.0', 'authorization' : 'Bearer ' + auth_stub.authToken} - r = requests.post(endpoint, data=json.dumps(payload), headers=headers) + r = requests.post(endpoint, data=json.dumps(payload), headers=headers, verify=ssl_verify) obj = super(ET_PostRest, self).__init__(r, True) return obj @@ -361,11 +361,11 @@ def __init__(self, auth_stub, endpoint, payload): ## ######## class ET_PatchRest(ET_Constructor): - def __init__(self, auth_stub, endpoint, payload): + def __init__(self, auth_stub, endpoint, payload, ssl_verify = True): auth_stub.refresh_token() headers = {'content-type' : 'application/json', 'user-agent' : 'FuelSDK-Python-v1.3.0', 'authorization' : 'Bearer ' + auth_stub.authToken} - r = requests.patch(endpoint , data=json.dumps(payload), headers=headers) + r = requests.patch(endpoint , data=json.dumps(payload), headers=headers, verify=ssl_verify) obj = super(ET_PatchRest, self).__init__(r, True) return obj @@ -376,11 +376,11 @@ def __init__(self, auth_stub, endpoint, payload): ## ######## class ET_DeleteRest(ET_Constructor): - def __init__(self, auth_stub, endpoint): + def __init__(self, auth_stub, endpoint, ssl_verify = True): auth_stub.refresh_token() headers = {'authorization' : 'Bearer ' + auth_stub.authToken, 'user-agent' : 'FuelSDK-Python-v1.3.0'} - r = requests.delete(endpoint, headers=headers) + r = requests.delete(endpoint, headers=headers, verify=ssl_verify) obj = super(ET_DeleteRest, self).__init__(r, True) return obj diff --git a/FuelSDK/unverified_https_transport.py b/FuelSDK/unverified_https_transport.py new file mode 100644 index 0000000..e3009e9 --- /dev/null +++ b/FuelSDK/unverified_https_transport.py @@ -0,0 +1,19 @@ +import urllib.request +import ssl +import suds.transport.http + +class UnverifiedHttpsTransport(suds.transport.http.HttpTransport): + """ + Used with the suds client to bypass SSL certificate validation + """ + + def __init__(self, *args, **kwargs): + super(UnverifiedHttpsTransport, self).__init__(*args, **kwargs) + + def u2handlers(self): + handlers = super(UnverifiedHttpsTransport, self).u2handlers() + context = ssl.create_default_context() + context.check_hostname = False + context.verify_mode = ssl.CERT_NONE + handlers.append(urllib.request.HTTPSHandler(context=context)) + return handlers \ No newline at end of file diff --git a/README.md b/README.md index 1f3af5c..30f75b1 100644 --- a/README.md +++ b/README.md @@ -34,6 +34,7 @@ New Features in Version 1.3.0 baseapiurl: soapendpoint: wsdl_file_local_loc: /ExactTargetWSDL.xml + ssl_verify: [Auth Service] useOAuth2Authentication: True @@ -42,6 +43,7 @@ New Features in Version 1.3.0 applicationType: redirectURI: authorizationCode: + ssl_verify: ``` Example passing config as a parameter to ET_Client constructor: @@ -63,6 +65,7 @@ New Features in Version 1.3.0 'applicationType': '' 'redirectURI': '' 'authorizationCode': '' + 'ssl_verify': }) ``` @@ -86,11 +89,13 @@ New Features in Version 1.2.0 baseapiurl: soapendpoint: wsdl_file_local_loc: /ExactTargetWSDL.xml + ssl_verify: [Auth Service] useOAuth2Authentication: True accountId: scope: + ssl_verify: ``` Example passing config as a parameter to ET_Client constructor: @@ -108,7 +113,8 @@ New Features in Version 1.2.0 'wsdl_file_local_loc': r'/ExactTargetWSDL.xml', 'useOAuth2Authentication': 'True', 'accountId': '', - 'scope': '' + 'scope': '', + 'ssl_verify': 'False' }) ```