Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 21 additions & 7 deletions SoftLayer/API.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
:license: MIT, see LICENSE for more details.
"""
import time
import warnings

from SoftLayer import auth as slauth
from SoftLayer import config
Expand Down Expand Up @@ -146,9 +147,6 @@ def call(self, service, method, *args, **kwargs):

headers = kwargs.get('headers', {})

if self.auth:
headers.update(self.auth.get_headers())

if kwargs.get('id') is not None:
headers[service + 'InitParameters'] = {'id': kwargs.get('id')}

Expand Down Expand Up @@ -178,11 +176,25 @@ def call(self, service, method, *args, **kwargs):
http_headers.update(kwargs.get('raw_headers'))

uri = '/'.join([self.endpoint_url, service])
options = {
'headers': headers,
'http_headers': http_headers,
'timeout': self.timeout,
'proxy': self.proxy,
}

if self.auth:
extra_headers = self.auth.get_headers()
if extra_headers:
warnings.warn("auth.get_headers() is deprecated and will be "
"removed in the next major version",
DeprecationWarning)
headers.update(extra_headers)

options = self.auth.get_options(options)

return transports.make_xml_rpc_api_call(uri, method, args,
headers=headers,
http_headers=http_headers,
timeout=self.timeout,
proxy=self.proxy)
**options)

__call__ = call

Expand Down Expand Up @@ -326,6 +338,8 @@ def call(self, name, *args, **kwargs):
:param int offset: (optional) offset results by this many
:param boolean iter: (optional) if True, returns a generator with the
results
:param bool verify: verify SSL cert
:param cert: client certificate path

Usage:
>>> import SoftLayer
Expand Down
45 changes: 28 additions & 17 deletions SoftLayer/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,22 @@

class AuthenticationBase(object):
"""A base authentication class intended to be overridden."""

def get_options(self, options):
"""Receives request options and returns request options.

:param options dict: dictionary of request options

"""
return options

def get_headers(self):
"""Return a dictionary of headers to be inserted for authentication."""
raise NotImplementedError
"""Return a dictionary of headers to be inserted for authentication.

.. deprecated:: 3.3.0
Use :func:`get_options` instead.
"""
return {}


class TokenAuthentication(AuthenticationBase):
Expand All @@ -26,15 +39,14 @@ def __init__(self, user_id, auth_token):
self.user_id = user_id
self.auth_token = auth_token

def get_headers(self):
"""Returns token-based auth headers."""
return {
'authenticate': {
'complexType': 'PortalLoginToken',
'userId': self.user_id,
'authToken': self.auth_token,
}
def get_options(self, options):
"""Sets token-based auth headers."""
options['headers']['authenticate'] = {
'complexType': 'PortalLoginToken',
'userId': self.user_id,
'authToken': self.auth_token,
}
return options

def __repr__(self):
return "<TokenAuthentication: %s %s>" % (self.user_id, self.auth_token)
Expand All @@ -50,14 +62,13 @@ def __init__(self, username, api_key):
self.username = username
self.api_key = api_key

def get_headers(self):
"""Returns token-based auth headers."""
return {
'authenticate': {
'username': self.username,
'apiKey': self.api_key,
}
def get_options(self, options):
"""Sets token-based auth headers."""
options['headers']['authenticate'] = {
'username': self.username,
'apiKey': self.api_key,
}
return options

def __repr__(self):
return "<BasicAuthentication: %s>" % (self.username)
6 changes: 3 additions & 3 deletions SoftLayer/tests/api_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@ def test_init(self):
client = SoftLayer.Client(username='doesnotexist',
api_key='issurelywrong', timeout=10)

auth_headers = {'authenticate': {'username': 'doesnotexist',
'apiKey': 'issurelywrong'}}
self.assertEqual(client.auth.get_headers(), auth_headers)
self.assertIsInstance(client.auth, SoftLayer.BasicAuthentication)
self.assertEqual(client.auth.username, 'doesnotexist')
self.assertEqual(client.auth.api_key, 'issurelywrong')
self.assertEqual(client.endpoint_url,
SoftLayer.API_PUBLIC_ENDPOINT.rstrip('/'))
self.assertEqual(client.timeout, 10)
Expand Down
33 changes: 20 additions & 13 deletions SoftLayer/tests/auth_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,10 @@


class TestAuthenticationBase(testing.TestCase):
def test_get_headers(self):
def test_get_options(self):
auth_base = auth.AuthenticationBase()
self.assertRaises(NotImplementedError, auth_base.get_headers)
self.assertEqual(auth_base.get_options({}), {})
self.assertEqual(auth_base.get_headers(), {})


class TestBasicAuthentication(testing.TestCase):
Expand All @@ -22,11 +23,14 @@ def test_attribs(self):
self.assertEqual(self.auth.username, 'USERNAME')
self.assertEqual(self.auth.api_key, 'APIKEY')

def test_get_headers(self):
self.assertEqual(self.auth.get_headers(), {
'authenticate': {
'username': 'USERNAME',
'apiKey': 'APIKEY',
def test_get_options(self):
headers = {'headers': {}}
self.assertEqual(self.auth.get_options(headers), {
'headers': {
'authenticate': {
'username': 'USERNAME',
'apiKey': 'APIKEY',
}
}
})

Expand All @@ -44,12 +48,15 @@ def test_attribs(self):
self.assertEqual(self.auth.user_id, 12345)
self.assertEqual(self.auth.auth_token, 'TOKEN')

def test_get_headers(self):
self.assertEqual(self.auth.get_headers(), {
'authenticate': {
'complexType': 'PortalLoginToken',
'userId': 12345,
'authToken': 'TOKEN',
def test_get_options(self):
headers = {'headers': {}}
self.assertEqual(self.auth.get_options(headers), {
'headers': {
'authenticate': {
'complexType': 'PortalLoginToken',
'userId': 12345,
'authToken': 'TOKEN',
}
}
})

Expand Down
43 changes: 43 additions & 0 deletions SoftLayer/tests/deprecated_tests.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
"""
SoftLayer.tests.depecated_tests
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

:license: MIT, see LICENSE for more details.
"""
import warnings

import mock

import SoftLayer
import SoftLayer.API
from SoftLayer import testing


class DeprecatedAuth(SoftLayer.AuthenticationBase):
"""Auth that only implements get_headers()."""

def get_headers(self):
return {'deprecated': 'header'}


class APIClient(testing.TestCase):
def set_up(self):
self.client = SoftLayer.Client(auth=DeprecatedAuth())

@mock.patch('SoftLayer.transports.make_xml_rpc_api_call')
def test_simple_call(self, make_xml_rpc_api_call):
with warnings.catch_warnings(record=True) as w:
# Cause all warnings to always be triggered.
warnings.simplefilter("always")

self.client['SERVICE'].METHOD()

make_xml_rpc_api_call.assert_called_with(
mock.ANY, mock.ANY, mock.ANY,
headers={'deprecated': 'header'},
proxy=mock.ANY,
timeout=mock.ANY,
http_headers=mock.ANY)
self.assertEqual(len(w), 1)
self.assertEqual(w[0].category, DeprecationWarning)
self.assertIn("deprecated", str(w[0].message))
8 changes: 6 additions & 2 deletions SoftLayer/tests/transport_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,9 @@ def test_call(self, request):
headers=None,
proxies=None,
data=data,
timeout=None)
timeout=None,
cert=None,
verify=True)
self.assertEqual(resp, [])

def test_proxy_without_protocol(self):
Expand All @@ -81,7 +83,9 @@ def test_valid_proxy(self, request):
proxies={'https': 'http://localhost:3128',
'http': 'http://localhost:3128'},
data=mock.ANY,
timeout=None)
timeout=None,
cert=None,
verify=True)


class TestRestAPICall(testing.TestCase):
Expand Down
13 changes: 9 additions & 4 deletions SoftLayer/transports.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,15 +23,18 @@ def _proxies_dict(proxy):
return {'http': proxy, 'https': proxy}


def make_xml_rpc_api_call(uri, method, args=None, headers=None,
http_headers=None, timeout=None, proxy=None):
def make_xml_rpc_api_call(url, method, args=None, headers=None,
http_headers=None, timeout=None, proxy=None,
verify=True, cert=None):
"""Makes a SoftLayer API call against the XML-RPC endpoint.

:param string uri: endpoint URL
:param string method: method to call E.G.: 'getObject'
:param dict headers: XML-RPC headers to use for the request
:param dict http_headers: HTTP headers to use for the request
:param int timeout: number of seconds to use as a timeout
:param bool verify: verify SSL cert
:param cert: client certificate path
"""
if args is None:
args = tuple()
Expand All @@ -43,14 +46,16 @@ def make_xml_rpc_api_call(uri, method, args=None, headers=None,
methodname=method,
allow_none=True)
LOGGER.debug("=== REQUEST ===")
LOGGER.info('POST %s', uri)
LOGGER.info('POST %s', url)
LOGGER.debug(http_headers)
LOGGER.debug(payload)

response = requests.request('POST', uri,
response = requests.request('POST', url,
data=payload,
headers=http_headers,
timeout=timeout,
verify=verify,
cert=cert,
proxies=_proxies_dict(proxy))
LOGGER.debug("=== RESPONSE ===")
LOGGER.debug(response.headers)
Expand Down