Merge "Separate the fetch and validate parts of auth_token"
This commit is contained in:
commit
5179a163de
@ -447,6 +447,32 @@ class _BaseAuthProtocol(object):
|
|||||||
By default this method does not return a value.
|
By default this method does not return a value.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
def _do_fetch_token(self, token):
|
||||||
|
"""Helper method to fetch a token and convert it into an AccessInfo"""
|
||||||
|
data = self._fetch_token(token)
|
||||||
|
|
||||||
|
try:
|
||||||
|
return data, access.AccessInfo.factory(body=data, auth_token=token)
|
||||||
|
except Exception:
|
||||||
|
self.log.warning(_LW('Invalid token contents.'), exc_info=True)
|
||||||
|
raise exc.InvalidToken(_('Token authorization failed'))
|
||||||
|
|
||||||
|
def _fetch_token(self, token):
|
||||||
|
"""Fetch the token data based on the value in the header.
|
||||||
|
|
||||||
|
Retrieve the data associated with the token value that was in the
|
||||||
|
header. This can be from PKI, contacting the identity server or
|
||||||
|
whatever is required.
|
||||||
|
|
||||||
|
:param str token: The token present in the request header.
|
||||||
|
|
||||||
|
:raises exc.InvalidToken: if token is invalid.
|
||||||
|
|
||||||
|
:returns: The token data
|
||||||
|
:rtype: dict
|
||||||
|
"""
|
||||||
|
raise NotImplemented()
|
||||||
|
|
||||||
def process_response(self, response):
|
def process_response(self, response):
|
||||||
"""Do whatever you'd like to the response.
|
"""Do whatever you'd like to the response.
|
||||||
|
|
||||||
@ -527,52 +553,52 @@ class AuthProtocol(_BaseAuthProtocol):
|
|||||||
self._token_cache.initialize(request.environ)
|
self._token_cache.initialize(request.environ)
|
||||||
request.remove_auth_headers()
|
request.remove_auth_headers()
|
||||||
|
|
||||||
|
user_auth_ref = None
|
||||||
|
serv_auth_ref = None
|
||||||
|
|
||||||
|
self.log.debug('Authenticating user token')
|
||||||
try:
|
try:
|
||||||
user_auth_ref = None
|
user_token = self._get_user_token_from_request(request)
|
||||||
serv_auth_ref = None
|
data, user_auth_ref = self._do_fetch_token(user_token)
|
||||||
|
self._validate_token(user_auth_ref)
|
||||||
|
self._confirm_token_bind(user_auth_ref, request)
|
||||||
|
except exc.InvalidToken:
|
||||||
|
if self._delay_auth_decision:
|
||||||
|
self.log.info(
|
||||||
|
_LI('Invalid user token - deferring reject '
|
||||||
|
'downstream'))
|
||||||
|
request.user_token_valid = False
|
||||||
|
else:
|
||||||
|
self.log.info(
|
||||||
|
_LI('Invalid user token - rejecting request'))
|
||||||
|
self._reject_request()
|
||||||
|
else:
|
||||||
|
request.environ['keystone.token_info'] = data
|
||||||
|
request.set_user_headers(user_auth_ref,
|
||||||
|
self._include_service_catalog)
|
||||||
|
|
||||||
|
if request.service_token is not None:
|
||||||
|
self.log.debug('Authenticating service token')
|
||||||
try:
|
try:
|
||||||
self.log.debug('Authenticating user token')
|
_ignore, serv_auth_ref = self._do_fetch_token(
|
||||||
user_token_info = self._get_user_token_from_request(request)
|
request.service_token)
|
||||||
user_auth_ref, user_token_info = self._validate_token(
|
self._validate_token(serv_auth_ref)
|
||||||
user_token_info, request)
|
self._confirm_token_bind(serv_auth_ref, request)
|
||||||
request.environ['keystone.token_info'] = user_token_info
|
|
||||||
request.set_user_headers(user_auth_ref,
|
|
||||||
self._include_service_catalog)
|
|
||||||
except exc.InvalidToken:
|
|
||||||
if self._delay_auth_decision:
|
|
||||||
self.log.info(
|
|
||||||
_LI('Invalid user token - deferring reject '
|
|
||||||
'downstream'))
|
|
||||||
request.headers['X-Identity-Status'] = 'Invalid'
|
|
||||||
else:
|
|
||||||
self.log.info(
|
|
||||||
_LI('Invalid user token - rejecting request'))
|
|
||||||
self._reject_request()
|
|
||||||
|
|
||||||
try:
|
|
||||||
self.log.debug('Authenticating service token')
|
|
||||||
if request.service_token is not None:
|
|
||||||
serv_auth_ref, serv_token_info = self._validate_token(
|
|
||||||
request.service_token, request)
|
|
||||||
request.set_service_headers(serv_auth_ref)
|
|
||||||
except exc.InvalidToken:
|
except exc.InvalidToken:
|
||||||
if self._delay_auth_decision:
|
if self._delay_auth_decision:
|
||||||
self.log.info(
|
self.log.info(
|
||||||
_LI('Invalid service token - deferring reject '
|
_LI('Invalid service token - deferring reject '
|
||||||
'downstream'))
|
'downstream'))
|
||||||
request.headers['X-Service-Identity-Status'] = 'Invalid'
|
request.service_token_valid = False
|
||||||
else:
|
else:
|
||||||
self.log.info(
|
self.log.info(
|
||||||
_LI('Invalid service token - rejecting request'))
|
_LI('Invalid service token - rejecting request'))
|
||||||
self._reject_request()
|
self._reject_request()
|
||||||
|
else:
|
||||||
|
request.set_service_headers(serv_auth_ref)
|
||||||
|
|
||||||
request.token_auth = _user_plugin.UserAuthPlugin(user_auth_ref,
|
request.token_auth = _user_plugin.UserAuthPlugin(user_auth_ref,
|
||||||
serv_auth_ref)
|
serv_auth_ref)
|
||||||
|
|
||||||
except exc.ServiceError as e:
|
|
||||||
self.log.critical(_LC('Unable to obtain admin token: %s'), e)
|
|
||||||
raise webob.exc.HTTPServiceUnavailable()
|
|
||||||
|
|
||||||
if self.log.isEnabledFor(logging.DEBUG):
|
if self.log.isEnabledFor(logging.DEBUG):
|
||||||
self.log.debug('Received request from %s' %
|
self.log.debug('Received request from %s' %
|
||||||
@ -657,15 +683,14 @@ class AuthProtocol(_BaseAuthProtocol):
|
|||||||
if cached:
|
if cached:
|
||||||
return cached
|
return cached
|
||||||
|
|
||||||
def _validate_token(self, token, request):
|
def _fetch_token(self, token):
|
||||||
"""Authenticate user token
|
"""Retrieve a token from either a PKI bundle or the identity server.
|
||||||
|
|
||||||
|
:param str token: token id
|
||||||
|
|
||||||
:param token: token id
|
|
||||||
:param request: incoming request
|
|
||||||
:returns: uncrypted body of the token if the token is valid
|
|
||||||
:raises exc.InvalidToken: if token is rejected
|
:raises exc.InvalidToken: if token is rejected
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
data = None
|
||||||
token_hashes = None
|
token_hashes = None
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@ -680,9 +705,6 @@ class AuthProtocol(_BaseAuthProtocol):
|
|||||||
# regardless of initial mechanism used to validate it,
|
# regardless of initial mechanism used to validate it,
|
||||||
# and needs to be checked.
|
# and needs to be checked.
|
||||||
self._revocations.check(token_hashes)
|
self._revocations.check(token_hashes)
|
||||||
|
|
||||||
auth_ref = access.AccessInfo.factory(body=data,
|
|
||||||
auth_token=token)
|
|
||||||
else:
|
else:
|
||||||
verified = None
|
verified = None
|
||||||
|
|
||||||
@ -701,38 +723,18 @@ class AuthProtocol(_BaseAuthProtocol):
|
|||||||
|
|
||||||
if verified is not None:
|
if verified is not None:
|
||||||
data = jsonutils.loads(verified)
|
data = jsonutils.loads(verified)
|
||||||
auth_ref = access.AccessInfo.factory(body=data,
|
|
||||||
auth_token=token)
|
|
||||||
else:
|
else:
|
||||||
auth_ref = self._identity_server.verify_token(token)
|
data = self._identity_server.verify_token(token)
|
||||||
if auth_ref:
|
|
||||||
if auth_ref.version == 'v2.0':
|
|
||||||
data = {'access': auth_ref}
|
|
||||||
else: # it's v3.
|
|
||||||
data = {'token': auth_ref}
|
|
||||||
else:
|
|
||||||
data = None
|
|
||||||
|
|
||||||
# 0 seconds of validity means is it valid right now.
|
|
||||||
if auth_ref.will_expire_soon(stale_duration=0):
|
|
||||||
raise exc.InvalidToken(_('Token authorization failed'))
|
|
||||||
|
|
||||||
if _token_is_v2(data) and not auth_ref.project_id:
|
|
||||||
msg = _('Unable to determine service tenancy.')
|
|
||||||
raise exc.InvalidToken(msg)
|
|
||||||
|
|
||||||
self._confirm_token_bind(auth_ref, request)
|
|
||||||
|
|
||||||
if not cached:
|
|
||||||
self._token_cache.store(token_hashes[0], data)
|
self._token_cache.store(token_hashes[0], data)
|
||||||
|
|
||||||
return auth_ref, data
|
|
||||||
except (exceptions.ConnectionRefused, exceptions.RequestTimeout):
|
except (exceptions.ConnectionRefused, exceptions.RequestTimeout):
|
||||||
self.log.debug('Token validation failure.', exc_info=True)
|
self.log.debug('Token validation failure.', exc_info=True)
|
||||||
self.log.warning(_LW('Authorization failed for token'))
|
self.log.warning(_LW('Authorization failed for token'))
|
||||||
raise exc.InvalidToken(_('Token authorization failed'))
|
raise exc.InvalidToken(_('Token authorization failed'))
|
||||||
except exc.ServiceError:
|
except exc.ServiceError as e:
|
||||||
raise
|
self.log.critical(_LC('Unable to obtain admin token: %s'), e)
|
||||||
|
raise webob.exc.HTTPServiceUnavailable()
|
||||||
except Exception:
|
except Exception:
|
||||||
self.log.debug('Token validation failure.', exc_info=True)
|
self.log.debug('Token validation failure.', exc_info=True)
|
||||||
if token_hashes:
|
if token_hashes:
|
||||||
@ -740,6 +742,17 @@ class AuthProtocol(_BaseAuthProtocol):
|
|||||||
self.log.warning(_LW('Authorization failed for token'))
|
self.log.warning(_LW('Authorization failed for token'))
|
||||||
raise exc.InvalidToken(_('Token authorization failed'))
|
raise exc.InvalidToken(_('Token authorization failed'))
|
||||||
|
|
||||||
|
return data
|
||||||
|
|
||||||
|
def _validate_token(self, auth_ref):
|
||||||
|
# 0 seconds of validity means is it valid right now.
|
||||||
|
if auth_ref.will_expire_soon(stale_duration=0):
|
||||||
|
raise exc.InvalidToken(_('Token authorization failed'))
|
||||||
|
|
||||||
|
if auth_ref.version == 'v2.0' and not auth_ref.project_id:
|
||||||
|
msg = _('Unable to determine service tenancy.')
|
||||||
|
raise exc.InvalidToken(msg)
|
||||||
|
|
||||||
def _invalid_user_token(self, msg=False):
|
def _invalid_user_token(self, msg=False):
|
||||||
# NOTE(jamielennox): use False as the default so that None is valid
|
# NOTE(jamielennox): use False as the default so that None is valid
|
||||||
if msg is False:
|
if msg is False:
|
||||||
|
@ -72,7 +72,14 @@ class _V2RequestStrategy(_RequestStrategy):
|
|||||||
super(_V2RequestStrategy, self).__init__(adap, **kwargs)
|
super(_V2RequestStrategy, self).__init__(adap, **kwargs)
|
||||||
self._client = v2_client.Client(session=adap)
|
self._client = v2_client.Client(session=adap)
|
||||||
|
|
||||||
self.verify_token = self._client.tokens.validate_access_info
|
def verify_token(self, token):
|
||||||
|
auth_ref = self._client.tokens.validate_access_info(token)
|
||||||
|
|
||||||
|
if not auth_ref:
|
||||||
|
msg = _('Failed to fetch token data from identity server')
|
||||||
|
raise exc.InvalidToken(msg)
|
||||||
|
|
||||||
|
return {'access': auth_ref}
|
||||||
|
|
||||||
def _fetch_signing_cert(self):
|
def _fetch_signing_cert(self):
|
||||||
return self._client.certificates.get_signing_certificate()
|
return self._client.certificates.get_signing_certificate()
|
||||||
@ -92,10 +99,17 @@ class _V3RequestStrategy(_RequestStrategy):
|
|||||||
super(_V3RequestStrategy, self).__init__(adap, **kwargs)
|
super(_V3RequestStrategy, self).__init__(adap, **kwargs)
|
||||||
self._client = v3_client.Client(session=adap)
|
self._client = v3_client.Client(session=adap)
|
||||||
|
|
||||||
self.verify_token = functools.partial(
|
def verify_token(self, token):
|
||||||
self._client.tokens.validate,
|
auth_ref = self._client.tokens.validate(
|
||||||
|
token,
|
||||||
include_catalog=self._include_service_catalog)
|
include_catalog=self._include_service_catalog)
|
||||||
|
|
||||||
|
if not auth_ref:
|
||||||
|
msg = _('Failed to fetch token data from identity server')
|
||||||
|
raise exc.InvalidToken(msg)
|
||||||
|
|
||||||
|
return {'token': auth_ref}
|
||||||
|
|
||||||
def _fetch_signing_cert(self):
|
def _fetch_signing_cert(self):
|
||||||
return self._client.simple_cert.get_certificates()
|
return self._client.simple_cert.get_certificates()
|
||||||
|
|
||||||
@ -214,6 +228,9 @@ class IdentityServer(object):
|
|||||||
else:
|
else:
|
||||||
return auth_ref
|
return auth_ref
|
||||||
|
|
||||||
|
msg = _('Failed to fetch token data from identity server')
|
||||||
|
raise exc.InvalidToken(msg)
|
||||||
|
|
||||||
def fetch_revocation_list(self):
|
def fetch_revocation_list(self):
|
||||||
try:
|
try:
|
||||||
data = self._request_strategy.fetch_revocation_list()
|
data = self._request_strategy.fetch_revocation_list()
|
||||||
|
@ -1033,29 +1033,6 @@ class CommonAuthTokenMiddlewareTest(object):
|
|||||||
self.call_middleware(headers={'X-Auth-Token': token})
|
self.call_middleware(headers={'X-Auth-Token': token})
|
||||||
self.assertRaises(exc.InvalidToken, self._get_cached_token, token)
|
self.assertRaises(exc.InvalidToken, self._get_cached_token, token)
|
||||||
|
|
||||||
def _test_memcache_set_invalid_signed(self, hash_algorithms=None,
|
|
||||||
exp_mode='md5'):
|
|
||||||
token = self.token_dict['signed_token_scoped_expired']
|
|
||||||
if hash_algorithms:
|
|
||||||
self.conf['hash_algorithms'] = ','.join(hash_algorithms)
|
|
||||||
self.set_middleware()
|
|
||||||
self.call_middleware(headers={'X-Auth-Token': token})
|
|
||||||
self.assertRaises(exc.InvalidToken,
|
|
||||||
self._get_cached_token, token, mode=exp_mode)
|
|
||||||
|
|
||||||
def test_memcache_set_invalid_signed(self):
|
|
||||||
self._test_memcache_set_invalid_signed()
|
|
||||||
|
|
||||||
def test_memcache_set_invalid_signed_sha256_md5(self):
|
|
||||||
hash_algorithms = ['sha256', 'md5']
|
|
||||||
self._test_memcache_set_invalid_signed(hash_algorithms=hash_algorithms,
|
|
||||||
exp_mode='sha256')
|
|
||||||
|
|
||||||
def test_memcache_set_invalid_signed_sha256(self):
|
|
||||||
hash_algorithms = ['sha256']
|
|
||||||
self._test_memcache_set_invalid_signed(hash_algorithms=hash_algorithms,
|
|
||||||
exp_mode='sha256')
|
|
||||||
|
|
||||||
def test_memcache_set_expired(self, extra_conf={}, extra_environ={}):
|
def test_memcache_set_expired(self, extra_conf={}, extra_environ={}):
|
||||||
token_cache_time = 10
|
token_cache_time = 10
|
||||||
conf = {
|
conf = {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user