From 9fda5add290365809e847a2a9c4dba47c1e94a4f Mon Sep 17 00:00:00 2001 From: Jamie Lennox Date: Mon, 3 Feb 2014 13:14:53 +1000 Subject: [PATCH] Use requests library in S3 middleware Convert the S3 token middleware to use the requests library. It does not validate certificates as part of this patch to keep new functionality isolated. Change-Id: I0f26c80a969b919de80410af0a80920bd8493191 Closes-Bug: #1275598 --- keystoneclient/middleware/s3_token.py | 66 +++++++++---------- .../tests/test_s3_token_middleware.py | 19 ++++++ 2 files changed, 52 insertions(+), 33 deletions(-) diff --git a/keystoneclient/middleware/s3_token.py b/keystoneclient/middleware/s3_token.py index f35b1545e..59dd331b5 100644 --- a/keystoneclient/middleware/s3_token.py +++ b/keystoneclient/middleware/s3_token.py @@ -33,11 +33,12 @@ This WSGI component: """ -import httplib import logging import urllib import webob +import requests + from keystoneclient.openstack.common import jsonutils @@ -105,16 +106,26 @@ class S3Token(object): self.logger.debug('Starting the %s component' % PROTOCOL_NAME) self.reseller_prefix = conf.get('reseller_prefix', 'AUTH_') # where to find the auth service (we use this to validate tokens) - self.auth_host = conf.get('auth_host') - self.auth_port = int(conf.get('auth_port', 35357)) - self.auth_protocol = conf.get('auth_protocol', 'https') - if self.auth_protocol == 'http': - self.http_client_class = httplib.HTTPConnection - else: - self.http_client_class = httplib.HTTPSConnection + + auth_host = conf.get('auth_host') + auth_port = int(conf.get('auth_port', 35357)) + auth_protocol = conf.get('auth_protocol', 'https') + + self.request_uri = '%s://%s:%s' % (auth_protocol, auth_host, auth_port) + # SSL - self.cert_file = conf.get('certfile') - self.key_file = conf.get('keyfile') + insecure = conf.get('insecure', False) + cert_file = conf.get('certfile') + key_file = conf.get('keyfile') + + if insecure: + self.verify = False + elif cert_file and key_file: + self.verify = (cert_file, key_file) + elif cert_file: + self.verify = cert_file + else: + self.verify = None def deny_request(self, code): error_table = { @@ -132,33 +143,22 @@ class S3Token(object): def _json_request(self, creds_json): headers = {'Content-Type': 'application/json'} - if self.auth_protocol == 'http': - conn = self.http_client_class(self.auth_host, self.auth_port) - else: - conn = self.http_client_class(self.auth_host, - self.auth_port, - self.key_file, - self.cert_file) try: - conn.request('POST', '/v2.0/s3tokens', - body=creds_json, - headers=headers) - response = conn.getresponse() - output = response.read() - except Exception as e: + response = requests.post('%s/v2.0/s3tokens' % self.request_uri, + headers=headers, data=creds_json, + verify=self.verify) + except requests.exceptions.RequestException as e: self.logger.info('HTTP connection exception: %s' % e) resp = self.deny_request('InvalidURI') raise ServiceError(resp) - finally: - conn.close() - if response.status < 200 or response.status >= 300: + if response.status_code < 200 or response.status_code >= 300: self.logger.debug('Keystone reply error: status=%s reason=%s' % - (response.status, response.reason)) + (response.status_code, response.reason)) resp = self.deny_request('AccessDenied') raise ServiceError(resp) - return (response, output) + return response def __call__(self, environ, start_response): """Handle incoming request. authenticate and send downstream.""" @@ -225,23 +225,23 @@ class S3Token(object): # identified and not doing a second query and just # pass it through to swiftauth in this case. try: - resp, output = self._json_request(creds_json) + resp = self._json_request(creds_json) except ServiceError as e: resp = e.args[0] msg = 'Received error, exiting middleware with error: %s' - self.logger.debug(msg % (resp.status)) + self.logger.debug(msg % (resp.status_code)) return resp(environ, start_response) self.logger.debug('Keystone Reply: Status: %d, Output: %s' % ( - resp.status, output)) + resp.status_code, resp.content)) try: - identity_info = jsonutils.loads(output) + identity_info = resp.json() token_id = str(identity_info['access']['token']['id']) tenant = identity_info['access']['token']['tenant'] except (ValueError, KeyError): error = 'Error on keystone reply: %d %s' - self.logger.debug(error % (resp.status, str(output))) + self.logger.debug(error % (resp.status_code, str(resp.content))) return self.deny_request('InvalidURI')(environ, start_response) req.headers['X-Auth-Token'] = token_id diff --git a/keystoneclient/tests/test_s3_token_middleware.py b/keystoneclient/tests/test_s3_token_middleware.py index 1f550d060..7362f84eb 100644 --- a/keystoneclient/tests/test_s3_token_middleware.py +++ b/keystoneclient/tests/test_s3_token_middleware.py @@ -16,6 +16,7 @@ import httpretty import mock +import requests import testtools import webob @@ -120,6 +121,24 @@ class S3TokenMiddlewareTestGood(S3TokenMiddlewareTestBase): path = req.environ['PATH_INFO'] self.assertTrue(path.startswith('/v1/AUTH_FORCED_TENANT_ID')) + @mock.patch.object(requests, 'post') + def test_insecure(self, MOCK_REQUEST): + self.middleware = ( + s3_token.filter_factory({'insecure': True})(FakeApp())) + + MOCK_REQUEST.return_value = utils.TestResponse({ + 'status_code': 201, + 'text': jsonutils.dumps(GOOD_RESPONSE)}) + + req = webob.Request.blank('/v1/AUTH_cfa/c/o') + req.headers['Authorization'] = 'access:signature' + req.headers['X-Storage-Token'] = 'token' + req.get_response(self.middleware) + + self.assertTrue(MOCK_REQUEST.called) + mock_args, mock_kwargs = MOCK_REQUEST.call_args + self.assertIs(mock_kwargs['verify'], False) + class S3TokenMiddlewareTestBad(S3TokenMiddlewareTestBase): def setUp(self):