From f45d7663f38bde35abfa223d185b6a4d5029c308 Mon Sep 17 00:00:00 2001 From: Brant Knudson Date: Thu, 19 Mar 2015 20:25:59 -0500 Subject: [PATCH] Change auth_token to use keystoneclient The auth_token middleware should use keystoneclient for all its communication with keystone. bp auth-token-use-client Depends-On: If802e8a47e45ae00112de3739334b4b5482d0500 Change-Id: Ib6ff5b0d9b260d03fe5d120fa47035886f43439c --- keystonemiddleware/auth_token/_auth.py | 11 ++- keystonemiddleware/auth_token/_identity.py | 99 +++++++------------ keystonemiddleware/auth_token/_utils.py | 7 -- .../auth_token/test_auth_token_middleware.py | 9 +- .../tests/unit/auth_token/test_utils.py | 6 -- 5 files changed, 48 insertions(+), 84 deletions(-) diff --git a/keystonemiddleware/auth_token/_auth.py b/keystonemiddleware/auth_token/_auth.py index ce2d37d2..cf7ed84d 100644 --- a/keystonemiddleware/auth_token/_auth.py +++ b/keystonemiddleware/auth_token/_auth.py @@ -96,7 +96,8 @@ class AuthTokenPlugin(auth.BaseAuthPlugin): :param session: The session object that the auth_plugin belongs to. :type session: keystoneclient.session.Session - :param tuple version: The version number required for this endpoint. + :param version: The version number required for this endpoint. + :type version: tuple or str :param str interface: what visibility the endpoint should have. :returns: The base URL that will be used to talk to the required @@ -123,9 +124,13 @@ class AuthTokenPlugin(auth.BaseAuthPlugin): # NOTE(jamielennox): for backwards compatibility here we don't # actually use the URL from discovery we hack it up instead. :( - if version[0] == 2: + # NOTE(blk-u): Normalizing the version is a workaround for bug 1450272. + # This can be removed once that's fixed. Also fix the docstring for the + # version parameter to be just "tuple". + version = discover.normalize_version_number(version) + if discover.version_match((2, 0), version): return '%s/v2.0' % self._identity_uri - elif version[0] == 3: + elif discover.version_match((3, 0), version): return '%s/v3' % self._identity_uri # NOTE(jamielennox): This plugin will only get called from auth_token diff --git a/keystonemiddleware/auth_token/_identity.py b/keystonemiddleware/auth_token/_identity.py index 8acf70d1..51c2b14c 100644 --- a/keystonemiddleware/auth_token/_identity.py +++ b/keystonemiddleware/auth_token/_identity.py @@ -13,12 +13,12 @@ from keystoneclient import auth from keystoneclient import discover from keystoneclient import exceptions -from oslo_serialization import jsonutils +from keystoneclient.v2_0 import client as v2_client +from keystoneclient.v3 import client as v3_client from six.moves import urllib from keystonemiddleware.auth_token import _auth from keystonemiddleware.auth_token import _exceptions as exc -from keystonemiddleware.auth_token import _utils from keystonemiddleware.i18n import _, _LE, _LI, _LW @@ -26,9 +26,7 @@ class _RequestStrategy(object): AUTH_VERSION = None - def __init__(self, json_request, adap, include_service_catalog=None): - self._json_request = json_request - self._adapter = adap + def __init__(self, adap, include_service_catalog=None): self._include_service_catalog = include_service_catalog def verify_token(self, user_token): @@ -42,36 +40,42 @@ class _V2RequestStrategy(_RequestStrategy): AUTH_VERSION = (2, 0) + def __init__(self, adap, **kwargs): + super(_V2RequestStrategy, self).__init__(adap, **kwargs) + self._client = v2_client.Client(session=adap) + def verify_token(self, user_token): - return self._json_request('GET', - '/tokens/%s' % user_token, - authenticated=True) + token = self._client.tokens.validate_access_info(user_token) + data = {'access': token} + return data def fetch_cert_file(self, cert_type): - return self._adapter.get('/certificates/%s' % cert_type, - authenticated=False) + if cert_type == 'ca': + return self._client.certificates.get_ca_certificate() + elif cert_type == 'signing': + return self._client.certificates.get_signing_certificate() class _V3RequestStrategy(_RequestStrategy): AUTH_VERSION = (3, 0) - def verify_token(self, user_token): - path = '/auth/tokens' - if not self._include_service_catalog: - path += '?nocatalog' + def __init__(self, adap, **kwargs): + super(_V3RequestStrategy, self).__init__(adap, **kwargs) + self._client = v3_client.Client(session=adap) - return self._json_request('GET', - path, - authenticated=True, - headers={'X-Subject-Token': user_token}) + def verify_token(self, user_token): + token = self._client.tokens.validate( + user_token, + include_catalog=self._include_service_catalog) + data = {'token': token} + return data def fetch_cert_file(self, cert_type): - if cert_type == 'signing': - cert_type = 'certificates' - - return self._adapter.get('/OS-SIMPLE-CERT/%s' % cert_type, - authenticated=False) + if cert_type == 'ca': + return self._client.simple_cert.get_ca_certificates() + elif cert_type == 'signing': + return self._client.simple_cert.get_certificates() _REQUEST_STRATEGIES = [_V3RequestStrategy, _V2RequestStrategy] @@ -97,6 +101,8 @@ class IdentityServer(object): # Built on-demand with self._request_strategy. self._request_strategy_obj = None + self._v2_client = v2_client.Client(session=self._adapter) + @property def auth_uri(self): auth_uri = self._adapter.get_endpoint(interface=auth.AUTH_INTERFACE) @@ -120,7 +126,6 @@ class IdentityServer(object): self._adapter.version = strategy_class.AUTH_VERSION self._request_strategy_obj = strategy_class( - self._json_request, self._adapter, include_service_catalog=self._include_service_catalog) @@ -163,10 +168,8 @@ class IdentityServer(object): :raises exc.ServiceError: if unable to authenticate token """ - user_token = _utils.safe_quote(user_token) - try: - response, data = self._request_strategy.verify_token(user_token) + data = self._request_strategy.verify_token(user_token) except exceptions.NotFound as e: self._LOG.warn(_LW('Authorization failed for token')) self._LOG.warn(_LW('Identity response: %s'), e.response.text) @@ -182,23 +185,14 @@ class IdentityServer(object): e.http_status) self._LOG.warn(_LW('Identity response: %s'), e.response.text) else: - if response.status_code == 200: - return data - - raise exc.InvalidToken() + return data def fetch_revocation_list(self): try: - response, data = self._json_request( - 'GET', '/tokens/revoked', - authenticated=True, - endpoint_filter={'version': (2, 0)}) + data = self._v2_client.tokens.get_revoked() except exceptions.HTTPError as e: msg = _('Failed to fetch token revocation list: %d') raise exc.RevocationListError(msg % e.http_status) - if response.status_code != 200: - msg = _('Unable to fetch token revocation list.') - raise exc.RevocationListError(msg) if 'signed' not in data: msg = _('Revocation list improperly formatted.') raise exc.RevocationListError(msg) @@ -210,34 +204,9 @@ class IdentityServer(object): def fetch_ca_cert(self): return self._fetch_cert_file('ca') - def _json_request(self, method, path, **kwargs): - """HTTP request helper used to make json requests. - - :param method: http method - :param path: relative request url - :param **kwargs: additional parameters used by session or endpoint - :returns: http response object, response body parsed as json - :raises ServerError: when unable to communicate with identity server. - - """ - headers = kwargs.setdefault('headers', {}) - headers['Accept'] = 'application/json' - - response = self._adapter.request(path, method, **kwargs) - - try: - data = jsonutils.loads(response.text) - except ValueError: - self._LOG.debug('Identity server did not return json-encoded body') - data = {} - - return response, data - def _fetch_cert_file(self, cert_type): try: - response = self._request_strategy.fetch_cert_file(cert_type) + text = self._request_strategy.fetch_cert_file(cert_type) except exceptions.HTTPError as e: raise exceptions.CertificateConfigError(e.details) - if response.status_code != 200: - raise exceptions.CertificateConfigError(response.text) - return response.text + return text diff --git a/keystonemiddleware/auth_token/_utils.py b/keystonemiddleware/auth_token/_utils.py index daed02dd..ce24e500 100644 --- a/keystonemiddleware/auth_token/_utils.py +++ b/keystonemiddleware/auth_token/_utils.py @@ -10,13 +10,6 @@ # License for the specific language governing permissions and limitations # under the License. -from six.moves import urllib - - -def safe_quote(s): - """URL-encode strings that are not already URL-encoded.""" - return urllib.parse.quote(s) if s == urllib.parse.unquote(s) else s - class MiniResp(object): diff --git a/keystonemiddleware/tests/unit/auth_token/test_auth_token_middleware.py b/keystonemiddleware/tests/unit/auth_token/test_auth_token_middleware.py index 95e58236..a859986b 100644 --- a/keystonemiddleware/tests/unit/auth_token/test_auth_token_middleware.py +++ b/keystonemiddleware/tests/unit/auth_token/test_auth_token_middleware.py @@ -1773,7 +1773,8 @@ class v3AuthTokenMiddlewareTest(BaseAuthTokenMiddlewareTest, text=self.examples.SIGNED_REVOCATION_LIST) self.requests_mock.get('%s/v3/auth/tokens' % BASE_URI, - text=self.token_response) + text=self.token_response, + headers={'X-Subject-Token': uuid.uuid4().hex}) self.set_middleware() @@ -2498,7 +2499,8 @@ class v3CompositeAuthTests(BaseAuthTokenMiddlewareTest, text=self.examples.SIGNED_REVOCATION_LIST) self.requests_mock.get('%s/v3/auth/tokens' % BASE_URI, - text=self.token_response) + text=self.token_response, + headers={'X-Subject-Token': uuid.uuid4().hex}) self.token_expected_env = dict(EXPECTED_V2_DEFAULT_ENV_RESPONSE) self.token_expected_env.update(EXPECTED_V3_DEFAULT_ENV_ADDITIONS) @@ -2629,7 +2631,8 @@ class AuthProtocolLoadingTests(BaseAuthTokenMiddlewareTest): self.requests_mock.get(self.CRUD_URL + '/v3/auth/tokens', request_headers=request_headers, - json=user_token) + json=user_token, + headers={'X-Subject-Token': uuid.uuid4().hex}) req = webob.Request.blank('/') req.headers['X-Auth-Token'] = user_token_id diff --git a/keystonemiddleware/tests/unit/auth_token/test_utils.py b/keystonemiddleware/tests/unit/auth_token/test_utils.py index fcd1e628..af590f32 100644 --- a/keystonemiddleware/tests/unit/auth_token/test_utils.py +++ b/keystonemiddleware/tests/unit/auth_token/test_utils.py @@ -17,12 +17,6 @@ from keystonemiddleware.auth_token import _utils class TokenEncodingTest(testtools.TestCase): - def test_unquoted_token(self): - self.assertEqual('foo%20bar', _utils.safe_quote('foo bar')) - - def test_quoted_token(self): - self.assertEqual('foo%20bar', _utils.safe_quote('foo%20bar')) - def test_messages_encoded_as_bytes(self): """Test that string are passed around as bytes for PY3.""" msg = "This is an error"