Replace the oauth library with oauthlib

Because the oauth library we were using doesn't support Python 3,
cloudbase-init could not work on Python 3 for the MaaS metadata service,
the only place in the code base where OAuth is needed. This patch
replaces oauth with oauthlib. oauthlib is better maintained and has support
for Python 2.6-3.4.

Change-Id: Iae2995420697bc305f2724ce038db2f2b3ab51e3
Closes-Bug: #1382572
This commit is contained in:
Claudiu Popa 2015-03-06 16:50:14 +02:00
parent 433d4093c7
commit a4f4a3c4e0
3 changed files with 49 additions and 54 deletions

View File

@ -13,9 +13,8 @@
# under the License.
import posixpath
import time
from oauth import oauth
from oauthlib import oauth1
from oslo.config import cfg
from six.moves.urllib import error
from six.moves.urllib import request
@ -43,6 +42,17 @@ CONF.register_opts(opts)
LOG = logging.getLogger(__name__)
class _Realm(str):
# There's a bug in oauthlib which ignores empty realm strings,
# by checking that the given realm is always True.
# This string class always returns True in a boolean context,
# making sure that an empty realm can be used by oauthlib.
def __bool__(self):
return True
__nonzero__ = __bool__
class MaaSHttpService(base.BaseMetadataService):
_METADATA_2012_03_01 = '2012-03-01'
@ -76,22 +86,15 @@ class MaaSHttpService(base.BaseMetadataService):
raise
def _get_oauth_headers(self, url):
consumer = oauth.OAuthConsumer(CONF.maas_oauth_consumer_key,
CONF.maas_oauth_consumer_secret)
token = oauth.OAuthToken(CONF.maas_oauth_token_key,
CONF.maas_oauth_token_secret)
parameters = {'oauth_version': "1.0",
'oauth_nonce': oauth.generate_nonce(),
'oauth_timestamp': int(time.time()),
'oauth_token': token.key,
'oauth_consumer_key': consumer.key}
req = oauth.OAuthRequest(http_url=url, parameters=parameters)
req.sign_request(oauth.OAuthSignatureMethod_PLAINTEXT(), consumer,
token)
return req.to_header()
client = oauth1.Client(
CONF.maas_oauth_consumer_key,
client_secret=CONF.maas_oauth_consumer_secret,
resource_owner_key=CONF.maas_oauth_token_key,
resource_owner_secret=CONF.maas_oauth_token_secret,
signature_method=oauth1.SIGNATURE_PLAINTEXT)
realm = _Realm("")
headers = client.sign(url, realm=realm)[1]
return headers
def _get_data(self, path):
norm_path = posixpath.join(CONF.maas_metadata_url, path)

View File

@ -14,7 +14,6 @@
import os
import posixpath
import sys
import unittest
try:
@ -25,12 +24,10 @@ from oslo.config import cfg
from six.moves.urllib import error
from cloudbaseinit.metadata.services import base
from cloudbaseinit.metadata.services import maasservice
from cloudbaseinit.tests import testutils
from cloudbaseinit.utils import x509constants
if sys.version_info < (3, 0):
# TODO(alexpilotti) replace oauth with a Python 3 compatible module
from cloudbaseinit.metadata.services import maasservice
CONF = cfg.CONF
@ -38,12 +35,7 @@ CONF = cfg.CONF
class MaaSHttpServiceTest(unittest.TestCase):
def setUp(self):
if sys.version_info < (3, 0):
self.mock_oauth = mock.MagicMock()
maasservice.oauth = self.mock_oauth
self._maasservice = maasservice.MaaSHttpService()
else:
self.skipTest("Python 3 is not yet supported for maasservice")
self._maasservice = maasservice.MaaSHttpService()
@mock.patch("cloudbaseinit.metadata.services.maasservice.MaaSHttpService"
"._get_data")
@ -91,32 +83,32 @@ class MaaSHttpServiceTest(unittest.TestCase):
'test other error', {}, None)
self._test_get_response(ret_val=err)
@mock.patch('time.time')
def test_get_oauth_headers(self, mock_time):
mock_token = mock.MagicMock()
mock_consumer = mock.MagicMock()
mock_req = mock.MagicMock()
self.mock_oauth.OAuthConsumer.return_value = mock_consumer
self.mock_oauth.OAuthToken.return_value = mock_token
self.mock_oauth.OAuthRequest.return_value = mock_req
mock_time.return_value = 0
self.mock_oauth.generate_nonce.return_value = 'fake nounce'
@testutils.ConfPatcher('maas_oauth_consumer_key', 'consumer_key')
@testutils.ConfPatcher('maas_oauth_consumer_secret', 'consumer_secret')
@testutils.ConfPatcher('maas_oauth_token_key', 'token_key')
@testutils.ConfPatcher('maas_oauth_token_secret', 'token_secret')
def test_get_oauth_headers(self):
response = self._maasservice._get_oauth_headers(url='196.254.196.254')
self.mock_oauth.OAuthConsumer.assert_called_once_with(
CONF.maas_oauth_consumer_key, CONF.maas_oauth_consumer_secret)
self.mock_oauth.OAuthToken.assert_called_once_with(
CONF.maas_oauth_token_key, CONF.maas_oauth_token_secret)
parameters = {'oauth_version': "1.0",
'oauth_nonce': 'fake nounce',
'oauth_timestamp': int(0),
'oauth_token': mock_token.key,
'oauth_consumer_key': mock_consumer.key}
self.mock_oauth.OAuthRequest.assert_called_once_with(
http_url='196.254.196.254', parameters=parameters)
mock_req.sign_request.assert_called_once_with(
self.mock_oauth.OAuthSignatureMethod_PLAINTEXT(), mock_consumer,
mock_token)
self.assertEqual(mock_req.to_header.return_value, response)
self.assertIsInstance(response, dict)
self.assertIn('Authorization', response)
auth = response['Authorization']
self.assertTrue(auth.startswith('OAuth'))
auth = auth[6:]
parts = [item.strip() for item in auth.split(",")]
auth_parts = dict(part.split("=") for part in parts)
required_headers = {
'oauth_token',
'oauth_consumer_key',
'oauth_signature',
}
self.assertTrue(required_headers.issubset(set(auth_parts)))
self.assertEqual('"token_key"', auth_parts['oauth_token'])
self.assertEqual('"consumer_key"', auth_parts['oauth_consumer_key'])
self.assertEqual('"consumer_secret%26token_secret"',
auth_parts['oauth_signature'])
@mock.patch("cloudbaseinit.metadata.services.maasservice.MaaSHttpService"
"._get_oauth_headers")

View File

@ -6,6 +6,6 @@ pyserial
oslo.config
six>=1.7.0
Babel>=1.3
oauth
oauthlib
netifaces
PyYAML