Privatize Everything

auth_token middleware has always been in a weird state where the
keystone developers decided the internals were private but other people
would use them.

Make everything about auth_token middleware private.

Change-Id: I83f8d65a7d6903553d77e3f9916ea753447dba3f
This commit is contained in:
Jamie Lennox 2014-07-02 09:48:41 +10:00 committed by Morgan Fainberg
parent 8e626d5b77
commit 4b6dc3f1bd
2 changed files with 350 additions and 339 deletions

View File

@ -195,7 +195,7 @@ from keystonemiddleware.openstack.common import timeutils
# deprecation works) then other projects will not be able to override the
# options via CONF.
opts = [
_OPTS = [
cfg.StrOpt('auth_admin_prefix',
default='',
help='Prefix to prepend at the beginning of the path. '
@ -333,13 +333,12 @@ opts = [
]
CONF = cfg.CONF
CONF.register_opts(opts, group='keystone_authtoken')
CONF.register_opts(_OPTS, group='keystone_authtoken')
LIST_OF_VERSIONS_TO_ATTEMPT = ['v2.0', 'v3.0']
CACHE_KEY_TEMPLATE = 'tokens/%s'
_LIST_OF_VERSIONS_TO_ATTEMPT = ['v2.0', 'v3.0']
class BIND_MODE:
class _BIND_MODE:
DISABLED = 'disabled'
PERMISSIVE = 'permissive'
STRICT = 'strict'
@ -347,7 +346,7 @@ class BIND_MODE:
KERBEROS = 'kerberos'
def will_expire_soon(expiry):
def _will_expire_soon(expiry):
"""Determines if expiration is about to occur.
:param expiry: a datetime of the expected expiration
@ -365,7 +364,7 @@ def _token_is_v3(token_info):
return ('token' in token_info)
def confirm_token_not_expired(data):
def _confirm_token_not_expired(data):
if not data:
raise InvalidUserToken('Token authorization failed')
if _token_is_v2(data):
@ -418,7 +417,7 @@ def _v3_to_v2_catalog(catalog):
return v2_services
def safe_quote(s):
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
@ -439,7 +438,7 @@ class NetworkError(Exception):
pass
class MiniResp(object):
class _MiniResp(object):
def __init__(self, error_message, env, headers=[]):
# The HEAD method is unique: it must never return a body, even if
# it reports an error (RFC-2616 clause 9.4). We relieve callers
@ -456,25 +455,26 @@ class AuthProtocol(object):
"""Auth Middleware that handles authenticating client calls."""
def __init__(self, app, conf):
self.LOG = logging.getLogger(conf.get('log_name', __name__))
self.LOG.info('Starting keystone auth_token middleware')
self.conf = conf
self.app = app
self._LOG = logging.getLogger(conf.get('log_name', __name__))
self._LOG.info('Starting keystone auth_token middleware')
self._conf = conf
self._app = app
# delay_auth_decision means we still allow unauthenticated requests
# through and we let the downstream service make the final decision
self.delay_auth_decision = (self._conf_get('delay_auth_decision') in
(True, 'true', 't', '1', 'on', 'yes', 'y'))
self._delay_auth_decision = (self._conf_get('delay_auth_decision') in
(True, 'true', 't', '1', 'on', 'yes', 'y')
)
# where to find the auth service (we use this to validate tokens)
self.identity_uri = self._conf_get('identity_uri')
self.auth_uri = self._conf_get('auth_uri')
self._identity_uri = self._conf_get('identity_uri')
self._auth_uri = self._conf_get('auth_uri')
# NOTE(jamielennox): it does appear here that our defaults arguments
# are backwards. We need to do it this way so that we can handle the
# same deprecation strategy for CONF and the conf variable.
if not self.identity_uri:
self.LOG.warning('Configuring admin URI using auth fragments. '
if not self._identity_uri:
self._LOG.warning('Configuring admin URI using auth fragments. '
'This is deprecated, use \'identity_uri\''
' instead.')
@ -489,16 +489,16 @@ class AuthProtocol(object):
# http://www.ietf.org/rfc/rfc2732.txt
auth_host = '[%s]' % auth_host
self.identity_uri = '%s://%s:%s' % (auth_protocol, auth_host,
self._identity_uri = '%s://%s:%s' % (auth_protocol, auth_host,
auth_port)
if auth_admin_prefix:
self.identity_uri = '%s/%s' % (self.identity_uri,
self._identity_uri = '%s/%s' % (self._identity_uri,
auth_admin_prefix.strip('/'))
else:
self.identity_uri = self.identity_uri.rstrip('/')
self._identity_uri = self._identity_uri.rstrip('/')
if self.auth_uri is None:
self.LOG.warning(
if self._auth_uri is None:
self._LOG.warning(
'Configuring auth_uri to point to the public identity '
'endpoint is required; clients may not be able to '
'authenticate against an admin endpoint')
@ -507,49 +507,50 @@ class AuthProtocol(object):
# documented in bug 1207517.
# NOTE(jamielennox): we urljoin '/' to get just the base URI as
# this is the original behaviour.
self.auth_uri = urllib.parse.urljoin(self.identity_uri, '/')
self.auth_uri = self.auth_uri.rstrip('/')
self._auth_uri = urllib.parse.urljoin(self._identity_uri, '/')
self._auth_uri = self._auth_uri.rstrip('/')
# SSL
self.cert_file = self._conf_get('certfile')
self.key_file = self._conf_get('keyfile')
self.ssl_ca_file = self._conf_get('cafile')
self.ssl_insecure = self._conf_get('insecure')
self._cert_file = self._conf_get('certfile')
self._key_file = self._conf_get('keyfile')
self._ssl_ca_file = self._conf_get('cafile')
self._ssl_insecure = self._conf_get('insecure')
# signing
self.signing_dirname = self._conf_get('signing_dir')
if self.signing_dirname is None:
self.signing_dirname = tempfile.mkdtemp(prefix='keystone-signing-')
self.LOG.info('Using %s as cache directory for signing certificate',
self.signing_dirname)
self.verify_signing_dir()
self._signing_dirname = self._conf_get('signing_dir')
if self._signing_dirname is None:
self._signing_dirname = tempfile.mkdtemp(
prefix='keystone-signing-')
self._LOG.info('Using %s as cache directory for signing certificate',
self._signing_dirname)
self._verify_signing_dir()
val = '%s/signing_cert.pem' % self.signing_dirname
self.signing_cert_file_name = val
val = '%s/cacert.pem' % self.signing_dirname
self.signing_ca_file_name = val
val = '%s/revoked.pem' % self.signing_dirname
self.revoked_file_name = val
val = '%s/signing_cert.pem' % self._signing_dirname
self._signing_cert_file_name = val
val = '%s/cacert.pem' % self._signing_dirname
self._signing_ca_file_name = val
val = '%s/revoked.pem' % self._signing_dirname
self._revoked_file_name = val
# Credentials used to verify this component with the Auth service since
# validating tokens is a privileged call
self.admin_token = self._conf_get('admin_token')
if self.admin_token:
self.LOG.warning(
self._admin_token = self._conf_get('admin_token')
if self._admin_token:
self._LOG.warning(
"The admin_token option in the auth_token middleware is "
"deprecated and should not be used. The admin_user and "
"admin_password options should be used instead. The "
"admin_token option may be removed in a future release.")
self.admin_token_expiry = None
self.admin_user = self._conf_get('admin_user')
self.admin_password = self._conf_get('admin_password')
self.admin_tenant_name = self._conf_get('admin_tenant_name')
self._admin_token_expiry = None
self._admin_user = self._conf_get('admin_user')
self._admin_password = self._conf_get('admin_password')
self._admin_tenant_name = self._conf_get('admin_tenant_name')
memcache_security_strategy = (
self._conf_get('memcache_security_strategy'))
self._token_cache = TokenCache(
self.LOG,
self._token_cache = _TokenCache(
self._LOG,
cache_time=int(self._conf_get('token_cache_time')),
hash_algorithms=self._conf_get('hash_algorithms'),
env_cache_name=self._conf_get('cache'),
@ -557,27 +558,27 @@ class AuthProtocol(object):
memcache_security_strategy=memcache_security_strategy,
memcache_secret_key=self._conf_get('memcache_secret_key'))
self._token_revocation_list = None
self._token_revocation_list_fetched_time = None
self.token_revocation_list_cache_timeout = datetime.timedelta(
self._token_revocation_list_prop = None
self._token_revocation_list_fetched_time_prop = None
self._token_revocation_list_cache_timeout = datetime.timedelta(
seconds=self._conf_get('revocation_cache_time'))
http_connect_timeout_cfg = self._conf_get('http_connect_timeout')
self.http_connect_timeout = (http_connect_timeout_cfg and
self._http_connect_timeout = (http_connect_timeout_cfg and
int(http_connect_timeout_cfg))
self.auth_version = None
self.http_request_max_retries = (
self._auth_version = None
self._http_request_max_retries = (
self._conf_get('http_request_max_retries'))
self.include_service_catalog = self._conf_get(
self._include_service_catalog = self._conf_get(
'include_service_catalog')
self.check_revocations_for_cached = self._conf_get(
self._check_revocations_for_cached = self._conf_get(
'check_revocations_for_cached')
def _conf_get(self, name):
# try config from paste-deploy first
if name in self.conf:
return self.conf[name]
if name in self._conf:
return self._conf[name]
else:
return CONF.keystone_authtoken[name]
@ -592,24 +593,24 @@ class AuthProtocol(object):
# server.
if self._conf_get('auth_version'):
version_to_use = self._conf_get('auth_version')
self.LOG.info('Auth Token proceeding with requested %s apis',
self._LOG.info('Auth Token proceeding with requested %s apis',
version_to_use)
else:
version_to_use = None
versions_supported_by_server = self._get_supported_versions()
if versions_supported_by_server:
for version in LIST_OF_VERSIONS_TO_ATTEMPT:
for version in _LIST_OF_VERSIONS_TO_ATTEMPT:
if version in versions_supported_by_server:
version_to_use = version
break
if version_to_use:
self.LOG.info('Auth Token confirmed use of %s apis',
self._LOG.info('Auth Token confirmed use of %s apis',
version_to_use)
else:
self.LOG.error(
self._LOG.error(
'Attempted versions [%s] not in list supported by '
'server [%s]',
', '.join(LIST_OF_VERSIONS_TO_ATTEMPT),
', '.join(_LIST_OF_VERSIONS_TO_ATTEMPT),
', '.join(versions_supported_by_server))
raise ServiceError('No compatible apis supported by server')
return version_to_use
@ -618,10 +619,11 @@ class AuthProtocol(object):
versions = []
response, data = self._json_request('GET', '/')
if response.status_code == 501:
self.LOG.warning('Old keystone installation found...assuming v2.0')
msg = 'Old keystone installation found...assuming v2.0'
self._LOG.warning(msg)
versions.append('v2.0')
elif response.status_code != 300:
self.LOG.error('Unable to get version info from keystone: %s',
self._LOG.error('Unable to get version info from keystone: %s',
response.status_code)
raise ServiceError('Unable to get version info from keystone')
else:
@ -629,12 +631,12 @@ class AuthProtocol(object):
for version in data['versions']['values']:
versions.append(version['id'])
except KeyError:
self.LOG.error(
self._LOG.error(
'Invalid version response format from server')
raise ServiceError('Unable to parse version response '
'from keystone')
self.LOG.debug('Server reports support for api versions: %s',
self._LOG.debug('Server reports support for api versions: %s',
', '.join(versions))
return versions
@ -645,7 +647,7 @@ class AuthProtocol(object):
we can't authenticate.
"""
self.LOG.debug('Authenticating user token')
self._LOG.debug('Authenticating user token')
self._token_cache.initialize(env)
@ -656,21 +658,21 @@ class AuthProtocol(object):
env['keystone.token_info'] = token_info
user_headers = self._build_user_headers(token_info)
self._add_headers(env, user_headers)
return self.app(env, start_response)
return self._app(env, start_response)
except InvalidUserToken:
if self.delay_auth_decision:
self.LOG.info(
if self._delay_auth_decision:
self._LOG.info(
'Invalid user token - deferring reject downstream')
self._add_headers(env, {'X-Identity-Status': 'Invalid'})
return self.app(env, start_response)
return self._app(env, start_response)
else:
self.LOG.info('Invalid user token - rejecting request')
self._LOG.info('Invalid user token - rejecting request')
return self._reject_request(env, start_response)
except ServiceError as e:
self.LOG.critical('Unable to obtain admin token: %s', e)
resp = MiniResp('Service unavailable', env)
self._LOG.critical('Unable to obtain admin token: %s', e)
resp = _MiniResp('Service unavailable', env)
start_response('503 Service Unavailable', resp.headers)
return resp.body
@ -701,7 +703,7 @@ class AuthProtocol(object):
'X-Tenant',
'X-Role',
)
self.LOG.debug('Removing headers from request environment: %s',
self._LOG.debug('Removing headers from request environment: %s',
','.join(auth_headers))
self._remove_headers(env, auth_headers)
@ -718,10 +720,10 @@ class AuthProtocol(object):
if token:
return token
else:
if not self.delay_auth_decision:
self.LOG.warn('Unable to find authentication token'
if not self._delay_auth_decision:
self._LOG.warn('Unable to find authentication token'
' in headers')
self.LOG.debug('Headers: %s', env)
self._LOG.debug('Headers: %s', env)
raise InvalidUserToken('Unable to find token in headers')
def _reject_request(self, env, start_response):
@ -732,12 +734,13 @@ class AuthProtocol(object):
:returns HTTPUnauthorized http response
"""
headers = [('WWW-Authenticate', 'Keystone uri=\'%s\'' % self.auth_uri)]
resp = MiniResp('Authentication required', env, headers)
header_val = 'Keystone uri=\'%s\'' % self._auth_uri
headers = [('WWW-Authenticate', header_val)]
resp = _MiniResp('Authentication required', env, headers)
start_response('401 Unauthorized', resp.headers)
return resp.body
def get_admin_token(self):
def _get_admin_token(self):
"""Return admin token, possibly fetching a new one.
if self.admin_token_expiry is set from fetching an admin token, check
@ -748,15 +751,15 @@ class AuthProtocol(object):
:raise ServiceError when unable to retrieve token from keystone
"""
if self.admin_token_expiry:
if will_expire_soon(self.admin_token_expiry):
self.admin_token = None
if self._admin_token_expiry:
if _will_expire_soon(self._admin_token_expiry):
self._admin_token = None
if not self.admin_token:
(self.admin_token,
self.admin_token_expiry) = self._request_admin_token()
if not self._admin_token:
(self._admin_token,
self._admin_token_expiry) = self._request_admin_token()
return self.admin_token
return self._admin_token
def _http_request(self, method, path, **kwargs):
"""HTTP request helper used to make unspecified content type requests.
@ -767,20 +770,20 @@ class AuthProtocol(object):
:raise ServerError when unable to communicate with keystone
"""
url = '%s/%s' % (self.identity_uri, path.lstrip('/'))
url = '%s/%s' % (self._identity_uri, path.lstrip('/'))
kwargs.setdefault('timeout', self.http_connect_timeout)
if self.cert_file and self.key_file:
kwargs['cert'] = (self.cert_file, self.key_file)
elif self.cert_file or self.key_file:
self.LOG.warn('Cannot use only a cert or key file. '
kwargs.setdefault('timeout', self._http_connect_timeout)
if self._cert_file and self._key_file:
kwargs['cert'] = (self._cert_file, self._key_file)
elif self._cert_file or self._key_file:
self._LOG.warn('Cannot use only a cert or key file. '
'Please provide both. Ignoring.')
kwargs['verify'] = self.ssl_ca_file or True
if self.ssl_insecure:
kwargs['verify'] = self._ssl_ca_file or True
if self._ssl_insecure:
kwargs['verify'] = False
RETRIES = self.http_request_max_retries
RETRIES = self._http_request_max_retries
retry = 0
while True:
try:
@ -788,10 +791,10 @@ class AuthProtocol(object):
break
except Exception as e:
if retry >= RETRIES:
self.LOG.error('HTTP connection exception: %s', e)
self._LOG.error('HTTP connection exception: %s', e)
raise NetworkError('Unable to communicate with keystone')
# NOTE(vish): sleep 0.5, 1, 2
self.LOG.warn('Retrying on HTTP connection exception: %s', e)
self._LOG.warn('Retrying on HTTP connection exception: %s', e)
time.sleep(2.0 ** retry / 2)
retry += 1
@ -827,7 +830,7 @@ class AuthProtocol(object):
try:
data = jsonutils.loads(response.text)
except ValueError:
self.LOG.debug('Keystone did not return json-encoded body')
self._LOG.debug('Keystone did not return json-encoded body')
data = {}
return response, data
@ -846,10 +849,10 @@ class AuthProtocol(object):
params = {
'auth': {
'passwordCredentials': {
'username': self.admin_user,
'password': self.admin_password,
'username': self._admin_user,
'password': self._admin_password,
},
'tenantName': self.admin_tenant_name,
'tenantName': self._admin_tenant_name,
}
}
@ -865,12 +868,12 @@ class AuthProtocol(object):
datetime_expiry = timeutils.parse_isotime(expiry)
return (token, timeutils.normalize_time(datetime_expiry))
except (AssertionError, KeyError):
self.LOG.warn(
self._LOG.warn(
'Unexpected response from keystone service: %s', data)
raise ServiceError('invalid json response')
except (ValueError):
data['access']['token']['id'] = '<SANITIZED>'
self.LOG.warn(
self._LOG.warn(
'Unable to parse expiration time from token: %s', data)
raise ServiceError('invalid json response')
@ -892,38 +895,38 @@ class AuthProtocol(object):
if cached:
data = cached
if self.check_revocations_for_cached:
if self._check_revocations_for_cached:
# A token stored in Memcached might have been revoked
# regardless of initial mechanism used to validate it,
# and needs to be checked.
for tid in token_ids:
is_revoked = self._is_token_id_in_revoked_list(tid)
if is_revoked:
self.LOG.debug(
self._LOG.debug(
'Token is marked as having been revoked')
raise InvalidUserToken(
'Token authorization failed')
elif cms.is_pkiz(user_token):
verified = self.verify_pkiz_token(user_token, token_ids)
verified = self._verify_pkiz_token(user_token, token_ids)
data = jsonutils.loads(verified)
elif cms.is_asn1_token(user_token):
verified = self.verify_signed_token(user_token, token_ids)
verified = self._verify_signed_token(user_token, token_ids)
data = jsonutils.loads(verified)
else:
data = self.verify_uuid_token(user_token, retry)
expires = confirm_token_not_expired(data)
data = self._verify_uuid_token(user_token, retry)
expires = _confirm_token_not_expired(data)
self._confirm_token_bind(data, env)
self._token_cache.store(token_id, data, expires)
return data
except NetworkError:
self.LOG.debug('Token validation failure.', exc_info=True)
self.LOG.warn('Authorization failed for token')
self._LOG.debug('Token validation failure.', exc_info=True)
self._LOG.warn('Authorization failed for token')
raise InvalidUserToken('Token authorization failed')
except Exception:
self.LOG.debug('Token validation failure.', exc_info=True)
self._LOG.debug('Token validation failure.', exc_info=True)
if token_id:
self._token_cache.store_invalid(token_id)
self.LOG.warn('Authorization failed for token')
self._LOG.warn('Authorization failed for token')
raise InvalidUserToken('Token authorization failed')
def _build_user_headers(self, token_info):
@ -963,11 +966,11 @@ class AuthProtocol(object):
'X-Role': roles,
}
self.LOG.debug('Received request from user: %s with project_id : %s'
self._LOG.debug('Received request from user: %s with project_id : %s'
' and roles: %s ',
auth_ref.user_id, auth_ref.project_id, roles)
if self.include_service_catalog and auth_ref.has_service_catalog():
if self._include_service_catalog and auth_ref.has_service_catalog():
catalog = auth_ref.service_catalog.get_data()
if _token_is_v3(token_info):
catalog = _v3_to_v2_catalog(catalog)
@ -1014,7 +1017,7 @@ class AuthProtocol(object):
def _confirm_token_bind(self, data, env):
bind_mode = self._conf_get('enforce_token_bind')
if bind_mode == BIND_MODE.DISABLED:
if bind_mode == _BIND_MODE.DISABLED:
return
try:
@ -1028,54 +1031,54 @@ class AuthProtocol(object):
bind = {}
# permissive and strict modes don't require there to be a bind
permissive = bind_mode in (BIND_MODE.PERMISSIVE, BIND_MODE.STRICT)
permissive = bind_mode in (_BIND_MODE.PERMISSIVE, _BIND_MODE.STRICT)
if not bind:
if permissive:
# no bind provided and none required
return
else:
self.LOG.info('No bind information present in token.')
self._LOG.info('No bind information present in token.')
self._invalid_user_token()
# get the named mode if bind_mode is not one of the predefined
if permissive or bind_mode == BIND_MODE.REQUIRED:
if permissive or bind_mode == _BIND_MODE.REQUIRED:
name = None
else:
name = bind_mode
if name and name not in bind:
self.LOG.info('Named bind mode %s not in bind information', name)
self._LOG.info('Named bind mode %s not in bind information', name)
self._invalid_user_token()
for bind_type, identifier in six.iteritems(bind):
if bind_type == BIND_MODE.KERBEROS:
if bind_type == _BIND_MODE.KERBEROS:
if not env.get('AUTH_TYPE', '').lower() == 'negotiate':
self.LOG.info('Kerberos credentials required and '
self._LOG.info('Kerberos credentials required and '
'not present.')
self._invalid_user_token()
if not env.get('REMOTE_USER') == identifier:
self.LOG.info('Kerberos credentials do not match '
self._LOG.info('Kerberos credentials do not match '
'those in bind.')
self._invalid_user_token()
self.LOG.debug('Kerberos bind authentication successful.')
self._LOG.debug('Kerberos bind authentication successful.')
elif bind_mode == BIND_MODE.PERMISSIVE:
self.LOG.debug('Ignoring Unknown bind for permissive mode: '
elif bind_mode == _BIND_MODE.PERMISSIVE:
self._LOG.debug('Ignoring Unknown bind for permissive mode: '
'%(bind_type)s: %(identifier)s.',
{'bind_type': bind_type,
'identifier': identifier})
else:
self.LOG.info('Couldn`t verify unknown bind: %(bind_type)s: '
self._LOG.info('Couldn`t verify unknown bind: %(bind_type)s: '
'%(identifier)s.',
{'bind_type': bind_type,
'identifier': identifier})
self._invalid_user_token()
def verify_uuid_token(self, user_token, retry=True):
def _verify_uuid_token(self, user_token, retry=True):
"""Authenticate user token with keystone.
:param user_token: user's token id
@ -1088,14 +1091,14 @@ class AuthProtocol(object):
"""
# Determine the highest api version we can use.
if not self.auth_version:
self.auth_version = self._choose_api_version()
if not self._auth_version:
self._auth_version = self._choose_api_version()
if self.auth_version == 'v3.0':
headers = {'X-Auth-Token': self.get_admin_token(),
'X-Subject-Token': safe_quote(user_token)}
if self._auth_version == 'v3.0':
headers = {'X-Auth-Token': self._get_admin_token(),
'X-Subject-Token': _safe_quote(user_token)}
path = '/v3/auth/tokens'
if not self.include_service_catalog:
if not self._include_service_catalog:
# NOTE(gyee): only v3 API support this option
path = path + '?nocatalog'
response, data = self._json_request(
@ -1103,43 +1106,42 @@ class AuthProtocol(object):
path,
additional_headers=headers)
else:
headers = {'X-Auth-Token': self.get_admin_token()}
headers = {'X-Auth-Token': self._get_admin_token()}
response, data = self._json_request(
'GET',
'/v2.0/tokens/%s' % safe_quote(user_token),
'/v2.0/tokens/%s' % _safe_quote(user_token),
additional_headers=headers)
if response.status_code == 200:
return data
if response.status_code == 404:
self.LOG.warn('Authorization failed for token')
self._LOG.warn('Authorization failed for token')
raise InvalidUserToken('Token authorization failed')
if response.status_code == 401:
self.LOG.info(
'Keystone rejected admin token, resetting')
self._LOG.info('Keystone rejected admin token, resetting')
self.admin_token = None
else:
self.LOG.error('Bad response code while validating token: %s',
self._LOG.error('Bad response code while validating token: %s',
response.status_code)
if retry:
self.LOG.info('Retrying validation')
return self.verify_uuid_token(user_token, False)
self._LOG.info('Retrying validation')
return self._verify_uuid_token(user_token, False)
else:
self.LOG.warn('Invalid user token. Keystone response: %s', data)
self._LOG.warn('Invalid user token. Keystone response: %s', data)
raise InvalidUserToken()
def is_signed_token_revoked(self, token_ids):
def _is_signed_token_revoked(self, token_ids):
"""Indicate whether the token appears in the revocation list."""
for token_id in token_ids:
if self._is_token_id_in_revoked_list(token_id):
self.LOG.debug('Token is marked as having been revoked')
self._LOG.debug('Token is marked as having been revoked')
return True
return False
def _is_token_id_in_revoked_list(self, token_id):
"""Indicate whether the token_id appears in the revocation list."""
revocation_list = self.token_revocation_list
revocation_list = self._token_revocation_list
revoked_tokens = revocation_list.get('revoked', None)
if not revoked_tokens:
return False
@ -1147,7 +1149,7 @@ class AuthProtocol(object):
revoked_ids = (x['id'] for x in revoked_tokens)
return token_id in revoked_ids
def cms_verify(self, data, inform=cms.PKI_ASN1_FORM):
def _cms_verify(self, data, inform=cms.PKI_ASN1_FORM):
"""Verifies the signature of the provided data's IAW CMS syntax.
If either of the certificate files might be missing, fetch them and
@ -1155,19 +1157,19 @@ class AuthProtocol(object):
"""
def verify():
try:
return cms.cms_verify(data, self.signing_cert_file_name,
self.signing_ca_file_name,
return cms.cms_verify(data, self._signing_cert_file_name,
self._signing_ca_file_name,
inform=inform).decode('utf-8')
except cms.subprocess.CalledProcessError as err:
self.LOG.warning('Verify error: %s', err)
self._LOG.warning('Verify error: %s', err)
raise
try:
return verify()
except exceptions.CertificateConfigError:
# the certs might be missing; unconditionally fetch to avoid racing
self.fetch_signing_cert()
self.fetch_ca_cert()
self._fetch_signing_cert()
self._fetch_ca_cert()
try:
# retry with certs in place
@ -1175,79 +1177,79 @@ class AuthProtocol(object):
except exceptions.CertificateConfigError as err:
# if this is still occurring, something else is wrong and we
# need err.output to identify the problem
self.LOG.error('CMS Verify output: %s', err.output)
self._LOG.error('CMS Verify output: %s', err.output)
raise
def verify_signed_token(self, signed_text, token_ids):
def _verify_signed_token(self, signed_text, token_ids):
"""Check that the token is unrevoked and has a valid signature."""
if self.is_signed_token_revoked(token_ids):
if self._is_signed_token_revoked(token_ids):
raise InvalidUserToken('Token has been revoked')
formatted = cms.token_to_cms(signed_text)
verified = self.cms_verify(formatted)
verified = self._cms_verify(formatted)
return verified
def verify_pkiz_token(self, signed_text, token_ids):
if self.is_signed_token_revoked(token_ids):
def _verify_pkiz_token(self, signed_text, token_ids):
if self._is_signed_token_revoked(token_ids):
raise InvalidUserToken('Token has been revoked')
try:
uncompressed = cms.pkiz_uncompress(signed_text)
verified = self.cms_verify(uncompressed, inform=cms.PKIZ_CMS_FORM)
verified = self._cms_verify(uncompressed, inform=cms.PKIZ_CMS_FORM)
return verified
# TypeError If the signed_text is not zlib compressed
except TypeError:
raise InvalidUserToken(signed_text)
def verify_signing_dir(self):
if os.path.exists(self.signing_dirname):
if not os.access(self.signing_dirname, os.W_OK):
def _verify_signing_dir(self):
if os.path.exists(self._signing_dirname):
if not os.access(self._signing_dirname, os.W_OK):
raise ConfigurationError(
'unable to access signing_dir %s' % self.signing_dirname)
'unable to access signing_dir %s' % self._signing_dirname)
uid = os.getuid()
if os.stat(self.signing_dirname).st_uid != uid:
self.LOG.warning(
'signing_dir is not owned by %s', uid)
current_mode = stat.S_IMODE(os.stat(self.signing_dirname).st_mode)
if os.stat(self._signing_dirname).st_uid != uid:
self._LOG.warning('signing_dir is not owned by %s', uid)
current_mode = stat.S_IMODE(os.stat(self._signing_dirname).st_mode)
if current_mode != stat.S_IRWXU:
self.LOG.warning(
self._LOG.warning(
'signing_dir mode is %s instead of %s',
oct(current_mode), oct(stat.S_IRWXU))
else:
os.makedirs(self.signing_dirname, stat.S_IRWXU)
os.makedirs(self._signing_dirname, stat.S_IRWXU)
@property
def token_revocation_list_fetched_time(self):
if not self._token_revocation_list_fetched_time:
def _token_revocation_list_fetched_time(self):
if not self._token_revocation_list_fetched_time_prop:
# If the fetched list has been written to disk, use its
# modification time.
if os.path.exists(self.revoked_file_name):
mtime = os.path.getmtime(self.revoked_file_name)
if os.path.exists(self._revoked_file_name):
mtime = os.path.getmtime(self._revoked_file_name)
fetched_time = datetime.datetime.utcfromtimestamp(mtime)
# Otherwise the list will need to be fetched.
else:
fetched_time = datetime.datetime.min
self._token_revocation_list_fetched_time = fetched_time
return self._token_revocation_list_fetched_time
self._token_revocation_list_fetched_time_prop = fetched_time
return self._token_revocation_list_fetched_time_prop
@token_revocation_list_fetched_time.setter
def token_revocation_list_fetched_time(self, value):
self._token_revocation_list_fetched_time = value
@_token_revocation_list_fetched_time.setter
def _token_revocation_list_fetched_time(self, value):
self._token_revocation_list_fetched_time_prop = value
@property
def token_revocation_list(self):
timeout = (self.token_revocation_list_fetched_time +
self.token_revocation_list_cache_timeout)
def _token_revocation_list(self):
timeout = (self._token_revocation_list_fetched_time +
self._token_revocation_list_cache_timeout)
list_is_current = timeutils.utcnow() < timeout
if list_is_current:
# Load the list from disk if required
if not self._token_revocation_list:
if not self._token_revocation_list_prop:
open_kwargs = {'encoding': 'utf-8'} if six.PY3 else {}
with open(self.revoked_file_name, 'r', **open_kwargs) as f:
self._token_revocation_list = jsonutils.loads(f.read())
with open(self._revoked_file_name, 'r', **open_kwargs) as f:
self._token_revocation_list_prop = jsonutils.loads(
f.read())
else:
self.token_revocation_list = self.fetch_revocation_list()
return self._token_revocation_list
self._token_revocation_list = self._fetch_revocation_list()
return self._token_revocation_list_prop
def _atomic_write_to_signing_dir(self, file_name, value):
# In Python2, encoding is slow so the following check avoids it if it
@ -1256,7 +1258,7 @@ class AuthProtocol(object):
value = value.encode('utf-8')
def _atomic_write(destination, data):
with tempfile.NamedTemporaryFile(dir=self.signing_dirname,
with tempfile.NamedTemporaryFile(dir=self._signing_dirname,
delete=False) as f:
f.write(data)
os.rename(f.name, destination)
@ -1264,41 +1266,41 @@ class AuthProtocol(object):
try:
_atomic_write(file_name, value)
except (OSError, IOError):
self.verify_signing_dir()
self._verify_signing_dir()
_atomic_write(file_name, value)
@token_revocation_list.setter
def token_revocation_list(self, value):
@_token_revocation_list.setter
def _token_revocation_list(self, value):
"""Save a revocation list to memory and to disk.
:param value: A json-encoded revocation list
"""
self._token_revocation_list = jsonutils.loads(value)
self.token_revocation_list_fetched_time = timeutils.utcnow()
self._atomic_write_to_signing_dir(self.revoked_file_name, value)
self._token_revocation_list_prop = jsonutils.loads(value)
self._token_revocation_list_fetched_time = timeutils.utcnow()
self._atomic_write_to_signing_dir(self._revoked_file_name, value)
def fetch_revocation_list(self, retry=True):
headers = {'X-Auth-Token': self.get_admin_token()}
def _fetch_revocation_list(self, retry=True):
headers = {'X-Auth-Token': self._get_admin_token()}
response, data = self._json_request('GET', '/v2.0/tokens/revoked',
additional_headers=headers)
if response.status_code == 401:
if retry:
self.LOG.info(
self._LOG.info(
'Keystone rejected admin token, resetting admin token')
self.admin_token = None
return self.fetch_revocation_list(retry=False)
self._admin_token = None
return self._fetch_revocation_list(retry=False)
if response.status_code != 200:
raise ServiceError('Unable to fetch token revocation list.')
if 'signed' not in data:
raise ServiceError('Revocation list improperly formatted.')
return self.cms_verify(data['signed'])
return self._cms_verify(data['signed'])
def _fetch_cert_file(self, cert_file_name, cert_type):
if not self.auth_version:
self.auth_version = self._choose_api_version()
if not self._auth_version:
self._auth_version = self._choose_api_version()
if self.auth_version == 'v3.0':
if self._auth_version == 'v3.0':
if cert_type == 'signing':
cert_type = 'certificates'
path = '/v3/OS-SIMPLE-CERT/' + cert_type
@ -1309,14 +1311,14 @@ class AuthProtocol(object):
raise exceptions.CertificateConfigError(response.text)
self._atomic_write_to_signing_dir(cert_file_name, response.text)
def fetch_signing_cert(self):
self._fetch_cert_file(self.signing_cert_file_name, 'signing')
def _fetch_signing_cert(self):
self._fetch_cert_file(self._signing_cert_file_name, 'signing')
def fetch_ca_cert(self):
self._fetch_cert_file(self.signing_ca_file_name, 'ca')
def _fetch_ca_cert(self):
self._fetch_cert_file(self._signing_ca_file_name, 'ca')
class CachePool(list):
class _CachePool(list):
"""A lazy pool of cache references."""
def __init__(self, cache, memcached_servers):
@ -1343,7 +1345,7 @@ class CachePool(list):
self.append(c)
class TokenCache(object):
class _TokenCache(object):
"""Encapsulates the auth_token token cache functionality.
auth_token caches tokens that it's seen so that when a token is re-used the
@ -1359,12 +1361,13 @@ class TokenCache(object):
"""
_CACHE_KEY_TEMPLATE = 'tokens/%s'
_INVALID_INDICATOR = 'invalid'
def __init__(self, log, cache_time=None, hash_algorithms=None,
env_cache_name=None, memcached_servers=None,
memcache_security_strategy=None, memcache_secret_key=None):
self.LOG = log
self._LOG = log
self._cache_time = cache_time
self._hash_algorithms = hash_algorithms
self._env_cache_name = env_cache_name
@ -1386,7 +1389,7 @@ class TokenCache(object):
if self._initialized:
return
self._cache_pool = CachePool(env.get(self._env_cache_name),
self._cache_pool = _CachePool(env.get(self._env_cache_name),
self._memcached_servers)
self._initialized = True
@ -1429,12 +1432,12 @@ class TokenCache(object):
quick check of token freshness on retrieval.
"""
self.LOG.debug('Storing token in cache')
self._LOG.debug('Storing token in cache')
self._cache_store(token_id, (data, expires))
def store_invalid(self, token_id):
"""Store invalid token in cache."""
self.LOG.debug('Marking token as unauthorized in cache')
self._LOG.debug('Marking token as unauthorized in cache')
self._cache_store(token_id, self._INVALID_INDICATOR)
def _assert_valid_memcache_protection_config(self):
@ -1459,7 +1462,7 @@ class TokenCache(object):
return
if self._memcache_security_strategy is None:
key = CACHE_KEY_TEMPLATE % token_id
key = self._CACHE_KEY_TEMPLATE % token_id
with self._cache_pool.reserve() as cache:
serialized = cache.get(key)
else:
@ -1473,7 +1476,7 @@ class TokenCache(object):
token_id,
secret_key,
security_strategy)
cache_key = CACHE_KEY_TEMPLATE % (
cache_key = self._CACHE_KEY_TEMPLATE % (
memcache_crypt.get_cache_key(keys))
with self._cache_pool.reserve() as cache:
raw_cached = cache.get(cache_key)
@ -1483,7 +1486,7 @@ class TokenCache(object):
raw_cached)
except Exception:
msg = 'Failed to decrypt/verify cache data'
self.LOG.exception(msg)
self._LOG.exception(msg)
# this should have the same effect as data not
# found in cache
serialized = None
@ -1498,7 +1501,7 @@ class TokenCache(object):
serialized = serialized.decode('utf-8')
cached = jsonutils.loads(serialized)
if cached == self._INVALID_INDICATOR:
self.LOG.debug('Cached Token is marked unauthorized')
self._LOG.debug('Cached Token is marked unauthorized')
raise InvalidUserToken('Token authorization failed')
data, expires = cached
@ -1514,10 +1517,10 @@ class TokenCache(object):
expires = timeutils.normalize_time(expires)
utcnow = timeutils.utcnow()
if utcnow < expires:
self.LOG.debug('Returning cached token')
self._LOG.debug('Returning cached token')
return data
else:
self.LOG.debug('Cached Token seems expired')
self._LOG.debug('Cached Token seems expired')
raise InvalidUserToken('Token authorization failed')
def _cache_store(self, token_id, data):
@ -1530,7 +1533,7 @@ class TokenCache(object):
if isinstance(serialized_data, six.text_type):
serialized_data = serialized_data.encode('utf-8')
if self._memcache_security_strategy is None:
cache_key = CACHE_KEY_TEMPLATE % token_id
cache_key = self._CACHE_KEY_TEMPLATE % token_id
data_to_store = serialized_data
else:
secret_key = self._memcache_secret_key
@ -1541,7 +1544,8 @@ class TokenCache(object):
security_strategy = security_strategy.encode('utf-8')
keys = memcache_crypt.derive_keys(
token_id, secret_key, security_strategy)
cache_key = CACHE_KEY_TEMPLATE % memcache_crypt.get_cache_key(keys)
cache_key = memcache_crypt.get_cache_key(keys)
cache_key = self._CACHE_KEY_TEMPLATE % cache_key
data_to_store = memcache_crypt.protect_data(keys, serialized_data)
with self._cache_pool.reserve() as cache:

View File

@ -250,15 +250,15 @@ class BaseAuthTokenMiddlewareTest(testtools.TestCase):
self.fake_app(self.expected_env), self.conf)
self.middleware._iso8601 = iso8601
with tempfile.NamedTemporaryFile(dir=self.middleware.signing_dirname,
with tempfile.NamedTemporaryFile(dir=self.middleware._signing_dirname,
delete=False) as f:
pass
self.middleware.revoked_file_name = f.name
self.middleware._revoked_file_name = f.name
self.addCleanup(cleanup_revoked_file,
self.middleware.revoked_file_name)
self.middleware._revoked_file_name)
self.middleware.token_revocation_list = jsonutils.dumps(
self.middleware._token_revocation_list = jsonutils.dumps(
{"revoked": [], "extra": "success"})
def start_fake_response(self, status, headers):
@ -295,8 +295,8 @@ class MultiStepAuthTokenMiddlewareTest(BaseAuthTokenMiddlewareTest,
"%s/v2.0/tokens/revoked" % BASE_URI,
responses=responses)
fetched_list = jsonutils.loads(self.middleware.fetch_revocation_list())
self.assertEqual(fetched_list, self.examples.REVOCATION_LIST)
fetched = jsonutils.loads(self.middleware._fetch_revocation_list())
self.assertEqual(fetched, self.examples.REVOCATION_LIST)
# Check that 4 requests have been made
self.assertEqual(len(httpretty.httpretty.latest_requests), 4)
@ -436,10 +436,10 @@ class GeneralAuthTokenMiddlewareTest(BaseAuthTokenMiddlewareTest,
def test_will_expire_soon(self):
tenseconds = datetime.datetime.utcnow() + datetime.timedelta(
seconds=10)
self.assertTrue(auth_token.will_expire_soon(tenseconds))
self.assertTrue(auth_token._will_expire_soon(tenseconds))
fortyseconds = datetime.datetime.utcnow() + datetime.timedelta(
seconds=40)
self.assertFalse(auth_token.will_expire_soon(fortyseconds))
self.assertFalse(auth_token._will_expire_soon(fortyseconds))
def test_token_is_v2_accepts_v2(self):
token = self.examples.UUID_TOKEN_DEFAULT
@ -557,7 +557,7 @@ class GeneralAuthTokenMiddlewareTest(BaseAuthTokenMiddlewareTest,
'auth_uri': 'https://keystone.example.com:1234',
}
middleware = auth_token.AuthProtocol(self.fake_app, conf)
self.assertEqual(middleware.token_revocation_list_cache_timeout,
self.assertEqual(middleware._token_revocation_list_cache_timeout,
datetime.timedelta(seconds=24))
@ -581,7 +581,7 @@ class CommonAuthTokenMiddlewareTest(object):
}
self.set_middleware(conf=conf)
expected_auth_uri = 'http://[2001:2013:1:f101::1]:1234'
self.assertEqual(expected_auth_uri, self.middleware.auth_uri)
self.assertEqual(expected_auth_uri, self.middleware._auth_uri)
def assert_valid_request_200(self, token, with_catalog=True):
req = webob.Request.blank('/')
@ -614,7 +614,7 @@ class CommonAuthTokenMiddlewareTest(object):
def _test_cache_revoked(self, token, revoked_form=None):
# When the token is cached and revoked, 401 is returned.
self.middleware.check_revocations_for_cached = True
self.middleware._check_revocations_for_cached = True
req = webob.Request.blank('/')
req.headers['X-Auth-Token'] = token
@ -624,7 +624,7 @@ class CommonAuthTokenMiddlewareTest(object):
self.assertEqual(200, self.response_status)
# Put it in revocation list.
self.middleware.token_revocation_list = self.get_revocation_list_json(
self.middleware._token_revocation_list = self.get_revocation_list_json(
token_ids=[revoked_form or token])
self.middleware(req.environ, self.start_fake_response)
self.assertEqual(401, self.response_status)
@ -647,7 +647,8 @@ class CommonAuthTokenMiddlewareTest(object):
self.assertLastPath(None)
def test_revoked_token_receives_401(self):
self.middleware.token_revocation_list = self.get_revocation_list_json()
self.middleware._token_revocation_list = (
self.get_revocation_list_json())
req = webob.Request.blank('/')
req.headers['X-Auth-Token'] = self.token_dict['revoked_token']
self.middleware(req.environ, self.start_fake_response)
@ -656,7 +657,7 @@ class CommonAuthTokenMiddlewareTest(object):
def test_revoked_token_receives_401_sha256(self):
self.conf['hash_algorithms'] = ['sha256', 'md5']
self.set_middleware()
self.middleware.token_revocation_list = (
self.middleware._token_revocation_list = (
self.get_revocation_list_json(mode='sha256'))
req = webob.Request.blank('/')
req.headers['X-Auth-Token'] = self.token_dict['revoked_token']
@ -675,7 +676,8 @@ class CommonAuthTokenMiddlewareTest(object):
# considered revoked so returns 401.
self.conf['hash_algorithms'] = ['sha256', 'md5']
self.set_middleware()
self.middleware.token_revocation_list = self.get_revocation_list_json()
self.middleware._token_revocation_list = (
self.get_revocation_list_json())
req = webob.Request.blank('/')
req.headers['X-Auth-Token'] = self.token_dict['revoked_token']
self.middleware(req.environ, self.start_fake_response)
@ -696,7 +698,7 @@ class CommonAuthTokenMiddlewareTest(object):
# Put the token in the revocation list.
token_hashed = cms.cms_hash_token(token)
self.middleware.token_revocation_list = self.get_revocation_list_json(
self.middleware._token_revocation_list = self.get_revocation_list_json(
token_ids=[token_hashed])
# First, request is using the hashed token, is valid so goes in
@ -723,50 +725,52 @@ class CommonAuthTokenMiddlewareTest(object):
def test_is_signed_token_revoked_returns_false(self):
#explicitly setting an empty revocation list here to document intent
self.middleware.token_revocation_list = jsonutils.dumps(
self.middleware._token_revocation_list = jsonutils.dumps(
{"revoked": [], "extra": "success"})
result = self.middleware.is_signed_token_revoked(
result = self.middleware._is_signed_token_revoked(
[self.token_dict['revoked_token_hash']])
self.assertFalse(result)
def test_is_signed_token_revoked_returns_true(self):
self.middleware.token_revocation_list = self.get_revocation_list_json()
result = self.middleware.is_signed_token_revoked(
self.middleware._token_revocation_list = (
self.get_revocation_list_json())
result = self.middleware._is_signed_token_revoked(
[self.token_dict['revoked_token_hash']])
self.assertTrue(result)
def test_is_signed_token_revoked_returns_true_sha256(self):
self.conf['hash_algorithms'] = ['sha256', 'md5']
self.set_middleware()
self.middleware.token_revocation_list = (
self.middleware._token_revocation_list = (
self.get_revocation_list_json(mode='sha256'))
result = self.middleware.is_signed_token_revoked(
result = self.middleware._is_signed_token_revoked(
[self.token_dict['revoked_token_hash_sha256']])
self.assertTrue(result)
def test_verify_signed_token_raises_exception_for_revoked_token(self):
self.middleware.token_revocation_list = self.get_revocation_list_json()
self.middleware._token_revocation_list = (
self.get_revocation_list_json())
self.assertRaises(auth_token.InvalidUserToken,
self.middleware.verify_signed_token,
self.middleware._verify_signed_token,
self.token_dict['revoked_token'],
[self.token_dict['revoked_token_hash']])
def test_verify_signed_token_raises_exception_for_revoked_token_s256(self):
self.conf['hash_algorithms'] = ['sha256', 'md5']
self.set_middleware()
self.middleware.token_revocation_list = (
self.middleware._token_revocation_list = (
self.get_revocation_list_json(mode='sha256'))
self.assertRaises(auth_token.InvalidUserToken,
self.middleware.verify_signed_token,
self.middleware._verify_signed_token,
self.token_dict['revoked_token'],
[self.token_dict['revoked_token_hash_sha256'],
self.token_dict['revoked_token_hash']])
def test_verify_signed_token_raises_exception_for_revoked_pkiz_token(self):
self.middleware.token_revocation_list = (
self.middleware._token_revocation_list = (
self.examples.REVOKED_TOKEN_PKIZ_LIST_JSON)
self.assertRaises(auth_token.InvalidUserToken,
self.middleware.verify_pkiz_token,
self.middleware._verify_pkiz_token,
self.token_dict['revoked_token_pkiz'],
[self.token_dict['revoked_token_pkiz_hash']])
@ -774,15 +778,17 @@ class CommonAuthTokenMiddlewareTest(object):
json.loads(text)
def test_verify_signed_token_succeeds_for_unrevoked_token(self):
self.middleware.token_revocation_list = self.get_revocation_list_json()
text = self.middleware.verify_signed_token(
self.middleware._token_revocation_list = (
self.get_revocation_list_json())
text = self.middleware._verify_signed_token(
self.token_dict['signed_token_scoped'],
[self.token_dict['signed_token_scoped_hash']])
self.assertIsValidJSON(text)
def test_verify_signed_compressed_token_succeeds_for_unrevoked_token(self):
self.middleware.token_revocation_list = self.get_revocation_list_json()
text = self.middleware.verify_pkiz_token(
self.middleware._token_revocation_list = (
self.get_revocation_list_json())
text = self.middleware._verify_pkiz_token(
self.token_dict['signed_token_scoped_pkiz'],
[self.token_dict['signed_token_scoped_hash']])
self.assertIsValidJSON(text)
@ -790,9 +796,9 @@ class CommonAuthTokenMiddlewareTest(object):
def test_verify_signed_token_succeeds_for_unrevoked_token_sha256(self):
self.conf['hash_algorithms'] = ['sha256', 'md5']
self.set_middleware()
self.middleware.token_revocation_list = (
self.middleware._token_revocation_list = (
self.get_revocation_list_json(mode='sha256'))
text = self.middleware.verify_signed_token(
text = self.middleware._verify_signed_token(
self.token_dict['signed_token_scoped'],
[self.token_dict['signed_token_scoped_hash_sha256'],
self.token_dict['signed_token_scoped_hash']])
@ -801,64 +807,65 @@ class CommonAuthTokenMiddlewareTest(object):
def test_verify_signing_dir_create_while_missing(self):
tmp_name = uuid.uuid4().hex
test_parent_signing_dir = "/tmp/%s" % tmp_name
self.middleware.signing_dirname = "/tmp/%s/%s" % ((tmp_name,) * 2)
self.middleware.signing_cert_file_name = (
"%s/test.pem" % self.middleware.signing_dirname)
self.middleware.verify_signing_dir()
self.middleware._signing_dirname = "/tmp/%s/%s" % ((tmp_name,) * 2)
self.middleware._signing_cert_file_name = (
"%s/test.pem" % self.middleware._signing_dirname)
self.middleware._verify_signing_dir()
# NOTE(wu_wenxiang): Verify if the signing dir was created as expected.
self.assertTrue(os.path.isdir(self.middleware.signing_dirname))
self.assertTrue(os.access(self.middleware.signing_dirname, os.W_OK))
self.assertEqual(os.stat(self.middleware.signing_dirname).st_uid,
self.assertTrue(os.path.isdir(self.middleware._signing_dirname))
self.assertTrue(os.access(self.middleware._signing_dirname, os.W_OK))
self.assertEqual(os.stat(self.middleware._signing_dirname).st_uid,
os.getuid())
self.assertEqual(
stat.S_IMODE(os.stat(self.middleware.signing_dirname).st_mode),
stat.S_IMODE(os.stat(self.middleware._signing_dirname).st_mode),
stat.S_IRWXU)
shutil.rmtree(test_parent_signing_dir)
def test_get_token_revocation_list_fetched_time_returns_min(self):
self.middleware.token_revocation_list_fetched_time = None
self.middleware.revoked_file_name = ''
self.assertEqual(self.middleware.token_revocation_list_fetched_time,
self.middleware._token_revocation_list_fetched_time = None
self.middleware._revoked_file_name = ''
self.assertEqual(self.middleware._token_revocation_list_fetched_time,
datetime.datetime.min)
def test_get_token_revocation_list_fetched_time_returns_mtime(self):
self.middleware.token_revocation_list_fetched_time = None
mtime = os.path.getmtime(self.middleware.revoked_file_name)
self.middleware._token_revocation_list_fetched_time = None
mtime = os.path.getmtime(self.middleware._revoked_file_name)
fetched_time = datetime.datetime.utcfromtimestamp(mtime)
self.assertEqual(fetched_time,
self.middleware.token_revocation_list_fetched_time)
self.middleware._token_revocation_list_fetched_time)
@testtools.skipUnless(TimezoneFixture.supported(),
'TimezoneFixture not supported')
def test_get_token_revocation_list_fetched_time_returns_utc(self):
with TimezoneFixture('UTC-1'):
self.middleware.token_revocation_list = jsonutils.dumps(
self.middleware._token_revocation_list = jsonutils.dumps(
self.examples.REVOCATION_LIST)
self.middleware.token_revocation_list_fetched_time = None
fetched_time = self.middleware.token_revocation_list_fetched_time
self.middleware._token_revocation_list_fetched_time = None
fetched_time = self.middleware._token_revocation_list_fetched_time
self.assertTrue(timeutils.is_soon(fetched_time, 1))
def test_get_token_revocation_list_fetched_time_returns_value(self):
expected = self.middleware._token_revocation_list_fetched_time
self.assertEqual(self.middleware.token_revocation_list_fetched_time,
self.assertEqual(self.middleware._token_revocation_list_fetched_time,
expected)
def test_get_revocation_list_returns_fetched_list(self):
# auth_token uses v2 to fetch this, so don't allow the v3
# tests to override the fake http connection
self.middleware.token_revocation_list_fetched_time = None
os.remove(self.middleware.revoked_file_name)
self.assertEqual(self.middleware.token_revocation_list,
self.middleware._token_revocation_list_fetched_time = None
os.remove(self.middleware._revoked_file_name)
self.assertEqual(self.middleware._token_revocation_list,
self.examples.REVOCATION_LIST)
def test_get_revocation_list_returns_current_list_from_memory(self):
self.assertEqual(self.middleware.token_revocation_list,
self.middleware._token_revocation_list)
self.assertEqual(self.middleware._token_revocation_list,
self.middleware._token_revocation_list_prop)
def test_get_revocation_list_returns_current_list_from_disk(self):
in_memory_list = self.middleware.token_revocation_list
self.middleware._token_revocation_list = None
self.assertEqual(self.middleware.token_revocation_list, in_memory_list)
in_memory_list = self.middleware._token_revocation_list
self.middleware._token_revocation_list_prop = None
self.assertEqual(self.middleware._token_revocation_list,
in_memory_list)
def test_invalid_revocation_list_raises_service_error(self):
httpretty.register_uri(httpretty.GET,
@ -867,13 +874,13 @@ class CommonAuthTokenMiddlewareTest(object):
status=200)
self.assertRaises(auth_token.ServiceError,
self.middleware.fetch_revocation_list)
self.middleware._fetch_revocation_list)
def test_fetch_revocation_list(self):
# auth_token uses v2 to fetch this, so don't allow the v3
# tests to override the fake http connection
fetched_list = jsonutils.loads(self.middleware.fetch_revocation_list())
self.assertEqual(fetched_list, self.examples.REVOCATION_LIST)
fetched = jsonutils.loads(self.middleware._fetch_revocation_list())
self.assertEqual(fetched, self.examples.REVOCATION_LIST)
def test_request_invalid_uuid_token(self):
# remember because we are testing the middleware we stub the connection
@ -923,12 +930,12 @@ class CommonAuthTokenMiddlewareTest(object):
def debug(self, msg=None, *args, **kwargs):
self.debugmsg = msg
self.middleware.LOG = FakeLog()
self.middleware.delay_auth_decision = False
self.middleware._LOG = FakeLog()
self.middleware._delay_auth_decision = False
self.assertRaises(auth_token.InvalidUserToken,
self.middleware._get_user_token_from_header, {})
self.assertIsNotNone(self.middleware.LOG.msg)
self.assertIsNotNone(self.middleware.LOG.debugmsg)
self.assertIsNotNone(self.middleware._LOG.msg)
self.assertIsNotNone(self.middleware._LOG.debugmsg)
def test_request_no_token_http(self):
req = webob.Request.blank('/', environ={'REQUEST_METHOD': 'HEAD'})
@ -1042,7 +1049,7 @@ class CommonAuthTokenMiddlewareTest(object):
"""
req = webob.Request.blank('/')
req.headers['X-Auth-Token'] = ERROR_TOKEN
self.middleware.http_request_max_retries = 0
self.middleware._http_request_max_retries = 0
self.middleware(req.environ, self.start_fake_response)
self.assertIsNone(self._get_cached_token(ERROR_TOKEN))
self.assert_valid_last_url(ERROR_TOKEN)
@ -1277,7 +1284,7 @@ class V2CertDownloadMiddlewareTest(BaseAuthTokenMiddlewareTest,
"%s%s" % (BASE_URI, self.signing_path),
status=404)
self.assertRaises(exceptions.CertificateConfigError,
self.middleware.verify_signed_token,
self.middleware._verify_signed_token,
self.examples.SIGNED_TOKEN_SCOPED,
[self.examples.SIGNED_TOKEN_SCOPED_HASH])
@ -1286,9 +1293,9 @@ class V2CertDownloadMiddlewareTest(BaseAuthTokenMiddlewareTest,
httpretty.register_uri(httpretty.GET,
"%s%s" % (BASE_URI, self.signing_path),
body=data)
self.middleware.fetch_signing_cert()
self.middleware._fetch_signing_cert()
with open(self.middleware.signing_cert_file_name, 'r') as f:
with open(self.middleware._signing_cert_file_name, 'r') as f:
self.assertEqual(f.read(), data)
self.assertEqual("/testadmin%s" % self.signing_path,
@ -1299,9 +1306,9 @@ class V2CertDownloadMiddlewareTest(BaseAuthTokenMiddlewareTest,
httpretty.register_uri(httpretty.GET,
"%s%s" % (BASE_URI, self.ca_path),
body=data)
self.middleware.fetch_ca_cert()
self.middleware._fetch_ca_cert()
with open(self.middleware.signing_ca_file_name, 'r') as f:
with open(self.middleware._signing_ca_file_name, 'r') as f:
self.assertEqual(f.read(), data)
self.assertEqual("/testadmin%s" % self.ca_path,
@ -1323,12 +1330,12 @@ class V2CertDownloadMiddlewareTest(BaseAuthTokenMiddlewareTest,
self.set_middleware(conf=self.conf)
self.middleware.fetch_ca_cert()
self.middleware._fetch_ca_cert()
self.assertEqual('/newadmin%s' % self.ca_path,
httpretty.last_request().path)
self.middleware.fetch_signing_cert()
self.middleware._fetch_signing_cert()
self.assertEqual('/newadmin%s' % self.signing_path,
httpretty.last_request().path)
@ -1349,12 +1356,12 @@ class V2CertDownloadMiddlewareTest(BaseAuthTokenMiddlewareTest,
self.set_middleware(conf=self.conf)
self.middleware.fetch_ca_cert()
self.middleware._fetch_ca_cert()
self.assertEqual(self.ca_path,
httpretty.last_request().path)
self.middleware.fetch_signing_cert()
self.middleware._fetch_signing_cert()
self.assertEqual(self.signing_path,
httpretty.last_request().path)
@ -1715,10 +1722,10 @@ class v3AuthTokenMiddlewareTest(BaseAuthTokenMiddlewareTest,
class TokenEncodingTest(testtools.TestCase):
def test_unquoted_token(self):
self.assertEqual('foo%20bar', auth_token.safe_quote('foo bar'))
self.assertEqual('foo%20bar', auth_token._safe_quote('foo bar'))
def test_quoted_token(self):
self.assertEqual('foo%20bar', auth_token.safe_quote('foo%20bar'))
self.assertEqual('foo%20bar', auth_token._safe_quote('foo%20bar'))
class TokenExpirationTest(BaseAuthTokenMiddlewareTest):
@ -1790,25 +1797,25 @@ class TokenExpirationTest(BaseAuthTokenMiddlewareTest):
def test_no_data(self):
data = {}
self.assertRaises(auth_token.InvalidUserToken,
auth_token.confirm_token_not_expired,
auth_token._confirm_token_not_expired,
data)
def test_bad_data(self):
data = {'my_happy_token_dict': 'woo'}
self.assertRaises(auth_token.InvalidUserToken,
auth_token.confirm_token_not_expired,
auth_token._confirm_token_not_expired,
data)
def test_v2_token_not_expired(self):
data = self.create_v2_token_fixture()
expected_expires = data['access']['token']['expires']
actual_expires = auth_token.confirm_token_not_expired(data)
actual_expires = auth_token._confirm_token_not_expired(data)
self.assertEqual(actual_expires, expected_expires)
def test_v2_token_expired(self):
data = self.create_v2_token_fixture(expires=self.one_hour_ago)
self.assertRaises(auth_token.InvalidUserToken,
auth_token.confirm_token_not_expired,
auth_token._confirm_token_not_expired,
data)
@mock.patch('keystonemiddleware.openstack.common.timeutils.utcnow')
@ -1819,7 +1826,7 @@ class TokenExpirationTest(BaseAuthTokenMiddlewareTest):
data = self.create_v2_token_fixture(
expires='2000-01-01T00:05:10.000123-05:00')
expected_expires = '2000-01-01T05:05:10.000123Z'
actual_expires = auth_token.confirm_token_not_expired(data)
actual_expires = auth_token._confirm_token_not_expired(data)
self.assertEqual(actual_expires, expected_expires)
@mock.patch('keystonemiddleware.openstack.common.timeutils.utcnow')
@ -1831,19 +1838,19 @@ class TokenExpirationTest(BaseAuthTokenMiddlewareTest):
expires='2000-01-01T00:05:10.000123+05:00')
data['access']['token']['expires'] = '2000-01-01T00:05:10.000123+05:00'
self.assertRaises(auth_token.InvalidUserToken,
auth_token.confirm_token_not_expired,
auth_token._confirm_token_not_expired,
data)
def test_v3_token_not_expired(self):
data = self.create_v3_token_fixture()
expected_expires = data['token']['expires_at']
actual_expires = auth_token.confirm_token_not_expired(data)
actual_expires = auth_token._confirm_token_not_expired(data)
self.assertEqual(actual_expires, expected_expires)
def test_v3_token_expired(self):
data = self.create_v3_token_fixture(expires=self.one_hour_ago)
self.assertRaises(auth_token.InvalidUserToken,
auth_token.confirm_token_not_expired,
auth_token._confirm_token_not_expired,
data)
@mock.patch('keystonemiddleware.openstack.common.timeutils.utcnow')
@ -1855,7 +1862,7 @@ class TokenExpirationTest(BaseAuthTokenMiddlewareTest):
expires='2000-01-01T00:05:10.000123-05:00')
expected_expires = '2000-01-01T05:05:10.000123Z'
actual_expires = auth_token.confirm_token_not_expired(data)
actual_expires = auth_token._confirm_token_not_expired(data)
self.assertEqual(actual_expires, expected_expires)
@mock.patch('keystonemiddleware.openstack.common.timeutils.utcnow')
@ -1866,7 +1873,7 @@ class TokenExpirationTest(BaseAuthTokenMiddlewareTest):
data = self.create_v3_token_fixture(
expires='2000-01-01T00:05:10.000123+05:00')
self.assertRaises(auth_token.InvalidUserToken,
auth_token.confirm_token_not_expired,
auth_token._confirm_token_not_expired,
data)
def test_cached_token_not_expired(self):