Skip to content

Commit 3aec9ec

Browse files
authored
Adds verify option to create_client_from_env (softlayer#773)
* Adds verify option to create_client_from_env. Resolves softlayer#772 * Add stronger warning for setting verify
1 parent 88a5b21 commit 3aec9ec

File tree

5 files changed

+134
-34
lines changed

5 files changed

+134
-34
lines changed

SoftLayer/API.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,8 @@ def create_client_from_env(username=None,
4646
config_file=None,
4747
proxy=None,
4848
user_agent=None,
49-
transport=None):
49+
transport=None,
50+
verify=True):
5051
"""Creates a SoftLayer API client using your environment.
5152
5253
Settings are loaded via keyword arguments, environemtal variables and
@@ -68,6 +69,8 @@ def create_client_from_env(username=None,
6869
calls if you wish to bypass the packages built in User Agent string
6970
:param transport: An object that's callable with this signature:
7071
transport(SoftLayer.transports.Request)
72+
:param bool verify: decide to verify the server's SSL/TLS cert. DO NOT SET
73+
TO FALSE WITHOUT UNDERSTANDING THE IMPLICATIONS.
7174
7275
Usage:
7376
@@ -83,6 +86,7 @@ def create_client_from_env(username=None,
8386
endpoint_url=endpoint_url,
8487
timeout=timeout,
8588
proxy=proxy,
89+
verify=verify,
8690
config_file=config_file)
8791

8892
if transport is None:
@@ -94,6 +98,7 @@ def create_client_from_env(username=None,
9498
proxy=settings.get('proxy'),
9599
timeout=settings.get('timeout'),
96100
user_agent=user_agent,
101+
verify=verify,
97102
)
98103
else:
99104
# Default the transport to use XMLRPC
@@ -102,6 +107,7 @@ def create_client_from_env(username=None,
102107
proxy=settings.get('proxy'),
103108
timeout=settings.get('timeout'),
104109
user_agent=user_agent,
110+
verify=verify,
105111
)
106112

107113
# If we have enough information to make an auth driver, let's do it
@@ -240,7 +246,8 @@ def call(self, service, method, *args, **kwargs):
240246
request.filter = kwargs.get('filter')
241247
request.limit = kwargs.get('limit')
242248
request.offset = kwargs.get('offset')
243-
request.verify = kwargs.get('verify')
249+
if kwargs.get('verify') is not None:
250+
request.verify = kwargs.get('verify')
244251

245252
if self.auth:
246253
extra_headers = self.auth.get_headers()

SoftLayer/transports.py

Lines changed: 24 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ def __init__(self):
6464
self.transport_headers = {}
6565

6666
#: Boolean specifying if the server certificate should be verified.
67-
self.verify = True
67+
self.verify = None
6868

6969
#: Client certificate file path.
7070
self.cert = None
@@ -103,13 +103,15 @@ def __init__(self,
103103
endpoint_url=None,
104104
timeout=None,
105105
proxy=None,
106-
user_agent=None):
106+
user_agent=None,
107+
verify=True):
107108

108109
self.endpoint_url = (endpoint_url or
109110
consts.API_PUBLIC_ENDPOINT).rstrip('/')
110111
self.timeout = timeout or None
111112
self.proxy = proxy
112113
self.user_agent = user_agent or consts.USER_AGENT
114+
self.verify = verify
113115

114116
def __call__(self, request):
115117
"""Makes a SoftLayer API call against the XML-RPC endpoint.
@@ -145,6 +147,12 @@ def __call__(self, request):
145147
payload = utils.xmlrpc_client.dumps(tuple(largs),
146148
methodname=request.method,
147149
allow_none=True)
150+
151+
# Prefer the request setting, if it's not None
152+
verify = request.verify
153+
if verify is None:
154+
verify = self.verify
155+
148156
LOGGER.debug("=== REQUEST ===")
149157
LOGGER.info('POST %s', url)
150158
LOGGER.debug(request.transport_headers)
@@ -155,7 +163,7 @@ def __call__(self, request):
155163
data=payload,
156164
headers=request.transport_headers,
157165
timeout=self.timeout,
158-
verify=request.verify,
166+
verify=verify,
159167
cert=request.cert,
160168
proxies=_proxies_dict(self.proxy))
161169
LOGGER.debug("=== RESPONSE ===")
@@ -202,13 +210,15 @@ def __init__(self,
202210
endpoint_url=None,
203211
timeout=None,
204212
proxy=None,
205-
user_agent=None):
213+
user_agent=None,
214+
verify=True):
206215

207216
self.endpoint_url = (endpoint_url or
208217
consts.API_PUBLIC_ENDPOINT_REST).rstrip('/')
209218
self.timeout = timeout or None
210219
self.proxy = proxy
211220
self.user_agent = user_agent or consts.USER_AGENT
221+
self.verify = verify
212222

213223
def __call__(self, request):
214224
"""Makes a SoftLayer API call against the REST endpoint.
@@ -269,6 +279,11 @@ def __call__(self, request):
269279

270280
url = '%s.%s' % ('/'.join(url_parts), 'json')
271281

282+
# Prefer the request setting, if it's not None
283+
verify = request.verify
284+
if verify is None:
285+
verify = self.verify
286+
272287
LOGGER.debug("=== REQUEST ===")
273288
LOGGER.info(url)
274289
LOGGER.debug(request.transport_headers)
@@ -280,24 +295,24 @@ def __call__(self, request):
280295
params=params,
281296
data=raw_body,
282297
timeout=self.timeout,
283-
verify=request.verify,
298+
verify=verify,
284299
cert=request.cert,
285300
proxies=_proxies_dict(self.proxy))
286301
LOGGER.debug("=== RESPONSE ===")
287302
LOGGER.debug(resp.headers)
288-
LOGGER.debug(resp.content)
303+
LOGGER.debug(resp.text)
289304
resp.raise_for_status()
290-
result = json.loads(resp.content)
305+
result = json.loads(resp.text)
291306

292307
if isinstance(result, list):
293308
return SoftLayerListResult(
294309
result, int(resp.headers.get('softlayer-total-items', 0)))
295310
else:
296311
return result
297312
except requests.HTTPError as ex:
298-
content = json.loads(ex.response.content)
313+
message = json.loads(ex.response.text)['error']
299314
raise exceptions.SoftLayerAPIError(ex.response.status_code,
300-
content['error'])
315+
message)
301316
except requests.RequestException as ex:
302317
raise exceptions.TransportError(0, str(ex))
303318

setup.cfg

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
[pytest]
1+
[tool:pytest]
22
python_files = *_tests.py
33

44
[wheel]

tests/api_tests.py

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@ class Inititialization(testing.TestCase):
1616
def test_init(self):
1717
client = SoftLayer.Client(username='doesnotexist',
1818
api_key='issurelywrong',
19-
timeout=10)
19+
timeout=10,
20+
endpoint_url='http://example.com/v3/xmlrpc/')
2021

2122
self.assertIsInstance(client.auth, SoftLayer.BasicAuthentication)
2223
self.assertEqual(client.auth.username, 'doesnotexist')
@@ -95,7 +96,7 @@ def test_simple_call(self):
9596
offset=None,
9697
)
9798

98-
def test_verify(self):
99+
def test_verify_request_false(self):
99100
client = SoftLayer.BaseClient(transport=self.mocks)
100101
mock = self.set_mock('SoftLayer_SERVICE', 'METHOD')
101102
mock.return_value = {"test": "result"}
@@ -105,6 +106,26 @@ def test_verify(self):
105106
self.assertEqual(resp, {"test": "result"})
106107
self.assert_called_with('SoftLayer_SERVICE', 'METHOD', verify=False)
107108

109+
def test_verify_request_true(self):
110+
client = SoftLayer.BaseClient(transport=self.mocks)
111+
mock = self.set_mock('SoftLayer_SERVICE', 'METHOD')
112+
mock.return_value = {"test": "result"}
113+
114+
resp = client.call('SERVICE', 'METHOD', verify=True)
115+
116+
self.assertEqual(resp, {"test": "result"})
117+
self.assert_called_with('SoftLayer_SERVICE', 'METHOD', verify=True)
118+
119+
def test_verify_request_not_specified(self):
120+
client = SoftLayer.BaseClient(transport=self.mocks)
121+
mock = self.set_mock('SoftLayer_SERVICE', 'METHOD')
122+
mock.return_value = {"test": "result"}
123+
124+
resp = client.call('SERVICE', 'METHOD')
125+
126+
self.assertEqual(resp, {"test": "result"})
127+
self.assert_called_with('SoftLayer_SERVICE', 'METHOD', verify=None)
128+
108129
@mock.patch('SoftLayer.API.BaseClient.iter_call')
109130
def test_iterate(self, _iter_call):
110131
self.client['SERVICE'].METHOD(iter=True)

tests/transport_tests.py

Lines changed: 77 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import warnings
99

1010
import mock
11+
import pytest
1112
import requests
1213
import six
1314

@@ -17,26 +18,31 @@
1718
from SoftLayer import transports
1819

1920

21+
def get_xmlrpc_response():
22+
response = requests.Response()
23+
list_body = six.b('''<?xml version="1.0" encoding="utf-8"?>
24+
<params>
25+
<param>
26+
<value>
27+
<array>
28+
<data/>
29+
</array>
30+
</value>
31+
</param>
32+
</params>''')
33+
response.raw = io.BytesIO(list_body)
34+
response.headers['SoftLayer-Total-Items'] = 10
35+
response.status_code = 200
36+
return response
37+
38+
2039
class TestXmlRpcAPICall(testing.TestCase):
2140

2241
def set_up(self):
2342
self.transport = transports.XmlRpcTransport(
2443
endpoint_url='http://something.com',
2544
)
26-
self.response = requests.Response()
27-
list_body = six.b('''<?xml version="1.0" encoding="utf-8"?>
28-
<params>
29-
<param>
30-
<value>
31-
<array>
32-
<data/>
33-
</array>
34-
</value>
35-
</param>
36-
</params>''')
37-
self.response.raw = io.BytesIO(list_body)
38-
self.response.headers['SoftLayer-Total-Items'] = 10
39-
self.response.status_code = 200
45+
self.response = get_xmlrpc_response()
4046

4147
@mock.patch('requests.request')
4248
def test_call(self, request):
@@ -251,6 +257,55 @@ def test_request_exception(self, request):
251257
self.assertRaises(SoftLayer.TransportError, self.transport, req)
252258

253259

260+
@mock.patch('requests.request')
261+
@pytest.mark.parametrize(
262+
"transport_verify,request_verify,expected",
263+
[
264+
(True, True, True),
265+
(True, False, False),
266+
(True, None, True),
267+
268+
(False, True, True),
269+
(False, False, False),
270+
(False, None, False),
271+
272+
(None, True, True),
273+
(None, False, False),
274+
(None, None, True),
275+
]
276+
)
277+
def test_verify(request,
278+
transport_verify,
279+
request_verify,
280+
expected):
281+
request.return_value = get_xmlrpc_response()
282+
283+
transport = transports.XmlRpcTransport(
284+
endpoint_url='http://something.com',
285+
)
286+
287+
req = transports.Request()
288+
req.service = 'SoftLayer_Service'
289+
req.method = 'getObject'
290+
291+
if request_verify is not None:
292+
req.verify = request_verify
293+
294+
if transport_verify is not None:
295+
transport.verify = transport_verify
296+
297+
transport(req)
298+
299+
request.assert_called_with('POST',
300+
'http://something.com/SoftLayer_Service',
301+
data=mock.ANY,
302+
headers=mock.ANY,
303+
cert=mock.ANY,
304+
proxies=mock.ANY,
305+
timeout=mock.ANY,
306+
verify=expected)
307+
308+
254309
class TestRestAPICall(testing.TestCase):
255310

256311
def set_up(self):
@@ -261,6 +316,7 @@ def set_up(self):
261316
@mock.patch('requests.request')
262317
def test_basic(self, request):
263318
request().content = '[]'
319+
request().text = '[]'
264320
request().headers = requests.structures.CaseInsensitiveDict({
265321
'SoftLayer-Total-Items': '10',
266322
})
@@ -290,7 +346,7 @@ def test_error(self, request):
290346
e = requests.HTTPError('error')
291347
e.response = mock.MagicMock()
292348
e.response.status_code = 404
293-
e.response.content = '''{
349+
e.response.text = '''{
294350
"error": "description",
295351
"code": "Error Code"
296352
}'''
@@ -315,14 +371,15 @@ def test_proxy_without_protocol(self):
315371

316372
@mock.patch('requests.request')
317373
def test_valid_proxy(self, request):
318-
request().content = '{}'
374+
request().text = '{}'
319375
self.transport.proxy = 'http://localhost:3128'
320376

321377
req = transports.Request()
322378
req.service = 'SoftLayer_Service'
323379
req.method = 'Resource'
324380

325381
self.transport(req)
382+
326383
request.assert_called_with(
327384
'GET', 'http://something.com/SoftLayer_Service/Resource.json',
328385
proxies={'https': 'http://localhost:3128',
@@ -337,7 +394,7 @@ def test_valid_proxy(self, request):
337394

338395
@mock.patch('requests.request')
339396
def test_with_id(self, request):
340-
request().content = '{}'
397+
request().text = '{}'
341398

342399
req = transports.Request()
343400
req.service = 'SoftLayer_Service'
@@ -361,7 +418,7 @@ def test_with_id(self, request):
361418

362419
@mock.patch('requests.request')
363420
def test_with_args(self, request):
364-
request().content = '{}'
421+
request().text = '{}'
365422

366423
req = transports.Request()
367424
req.service = 'SoftLayer_Service'
@@ -385,7 +442,7 @@ def test_with_args(self, request):
385442

386443
@mock.patch('requests.request')
387444
def test_with_filter(self, request):
388-
request().content = '{}'
445+
request().text = '{}'
389446

390447
req = transports.Request()
391448
req.service = 'SoftLayer_Service'
@@ -410,7 +467,7 @@ def test_with_filter(self, request):
410467

411468
@mock.patch('requests.request')
412469
def test_with_mask(self, request):
413-
request().content = '{}'
470+
request().text = '{}'
414471

415472
req = transports.Request()
416473
req.service = 'SoftLayer_Service'

0 commit comments

Comments
 (0)