Merge "Refactor request methods onto request object"

This commit is contained in:
Jenkins 2015-06-11 03:45:25 +00:00 committed by Gerrit Code Review
commit 6661501498
4 changed files with 404 additions and 217 deletions

View File

@ -219,6 +219,7 @@ from keystonemiddleware.auth_token import _base
from keystonemiddleware.auth_token import _cache
from keystonemiddleware.auth_token import _exceptions as exc
from keystonemiddleware.auth_token import _identity
from keystonemiddleware.auth_token import _request
from keystonemiddleware.auth_token import _revocations
from keystonemiddleware.auth_token import _signing_dir
from keystonemiddleware.auth_token import _user_plugin
@ -363,26 +364,6 @@ CONF.register_opts(_OPTS, group=_base.AUTHTOKEN_GROUP)
_LOG = logging.getLogger(__name__)
_HEADER_TEMPLATE = {
'X%s-Domain-Id': 'domain_id',
'X%s-Domain-Name': 'domain_name',
'X%s-Project-Id': 'project_id',
'X%s-Project-Name': 'project_name',
'X%s-Project-Domain-Id': 'project_domain_id',
'X%s-Project-Domain-Name': 'project_domain_name',
'X%s-User-Id': 'user_id',
'X%s-User-Name': 'username',
'X%s-User-Domain-Id': 'user_domain_id',
'X%s-User-Domain-Name': 'user_domain_name',
}
_DEPRECATED_HEADER_TEMPLATE = {
'X-User': 'username',
'X-Tenant-Id': 'project_id',
'X-Tenant-Name': 'project_name',
'X-Tenant': 'project_name',
}
class _BIND_MODE(object):
DISABLED = 'disabled'
@ -400,42 +381,6 @@ def _token_is_v3(token_info):
return ('token' in token_info)
def _v3_to_v2_catalog(catalog):
"""Convert a catalog to v2 format.
X_SERVICE_CATALOG must be specified in v2 format. If you get a token
that is in v3 convert it.
"""
v2_services = []
for v3_service in catalog:
# first copy over the entries we allow for the service
v2_service = {'type': v3_service['type']}
try:
v2_service['name'] = v3_service['name']
except KeyError:
pass
# now convert the endpoints. Because in v3 we specify region per
# URL not per group we have to collect all the entries of the same
# region together before adding it to the new service.
regions = {}
for v3_endpoint in v3_service.get('endpoints', []):
region_name = v3_endpoint.get('region')
try:
region = regions[region_name]
except KeyError:
region = {'region': region_name} if region_name else {}
regions[region_name] = region
interface_name = v3_endpoint['interface'].lower() + 'URL'
region[interface_name] = v3_endpoint['url']
v2_service['endpoints'] = list(regions.values())
v2_services.append(v2_service)
return v2_services
def _conf_values_type_convert(conf):
"""Convert conf values into correct type."""
if not conf:
@ -518,7 +463,6 @@ class AuthProtocol(object):
self._check_revocations_for_cached = self._conf_get(
'check_revocations_for_cached')
self._init_auth_headers()
def _conf_get(self, name, group=_base.AUTHTOKEN_GROUP):
# try config from paste-deploy first
@ -527,7 +471,7 @@ class AuthProtocol(object):
else:
return CONF[group][name]
@webob.dec.wsgify
@webob.dec.wsgify(RequestClass=_request._AuthTokenRequest)
def __call__(self, request):
"""Handle incoming request.
@ -536,7 +480,7 @@ class AuthProtocol(object):
"""
self._token_cache.initialize(request.environ)
self._remove_auth_headers(request)
request.remove_auth_headers()
try:
user_auth_ref = None
@ -548,8 +492,8 @@ class AuthProtocol(object):
user_auth_ref, user_token_info = self._validate_token(
user_token_info, request.environ)
request.environ['keystone.token_info'] = user_token_info
user_headers = self._build_user_headers(user_auth_ref)
request.headers.update(user_headers)
request.set_user_headers(user_auth_ref,
self._include_service_catalog)
except exc.InvalidToken:
if self._delay_auth_decision:
self._LOG.info(
@ -567,8 +511,7 @@ class AuthProtocol(object):
if serv_token is not None:
serv_auth_ref, serv_token_info = self._validate_token(
serv_token, request.environ)
serv_headers = self._build_service_headers(serv_auth_ref)
request.headers.update(serv_headers)
request.set_service_headers(serv_auth_ref)
except exc.InvalidToken:
if self._delay_auth_decision:
self._LOG.info(
@ -597,41 +540,6 @@ class AuthProtocol(object):
return response
def _init_auth_headers(self):
"""Initialize auth header list.
Both user and service token headers are generated.
"""
auth_headers = ['X-Service-Catalog',
'X-Identity-Status',
'X-Service-Identity-Status',
'X-Roles',
'X-Service-Roles']
for key in six.iterkeys(_HEADER_TEMPLATE):
auth_headers.append(key % '')
# Service headers
auth_headers.append(key % '-Service')
# Deprecated headers
auth_headers.append('X-Role')
for key in six.iterkeys(_DEPRECATED_HEADER_TEMPLATE):
auth_headers.append(key)
self._auth_headers = auth_headers
def _remove_auth_headers(self, request):
"""Remove headers so a user can't fake authentication.
Both user and service token headers are removed.
:param env: wsgi request environment
"""
self._LOG.debug('Removing headers from request environment: %s',
','.join(self._auth_headers))
for k in self._auth_headers:
request.headers.pop(k, None)
def _get_user_token_from_request(self, request):
"""Get token id from request.
@ -783,61 +691,6 @@ class AuthProtocol(object):
self._LOG.warning(_LW('Authorization failed for token'))
raise exc.InvalidToken(_('Token authorization failed'))
def _build_user_headers(self, auth_ref):
"""Convert token object into headers.
Build headers that represent authenticated user - see main
doc info at start of file for details of headers to be defined.
:param token_info: token object returned by identity
server on authentication
:raises exc.InvalidToken: when unable to parse token object
"""
roles = ','.join(auth_ref.role_names)
rval = {
'X-Identity-Status': 'Confirmed',
'X-Roles': roles,
}
for header_tmplt, attr in six.iteritems(_HEADER_TEMPLATE):
rval[header_tmplt % ''] = getattr(auth_ref, attr)
# Deprecated headers
rval['X-Role'] = roles
for header_tmplt, attr in six.iteritems(_DEPRECATED_HEADER_TEMPLATE):
rval[header_tmplt] = getattr(auth_ref, attr)
if self._include_service_catalog and auth_ref.has_service_catalog():
catalog = auth_ref.service_catalog.get_data()
if auth_ref.version == 'v3':
catalog = _v3_to_v2_catalog(catalog)
rval['X-Service-Catalog'] = jsonutils.dumps(catalog)
return rval
def _build_service_headers(self, auth_ref):
"""Convert token object into service headers.
Build headers that represent authenticated user - see main
doc info at start of file for details of headers to be defined.
:param auth_ref: authentication information
"""
roles = ','.join(auth_ref.role_names)
rval = {
'X-Service-Identity-Status': 'Confirmed',
'X-Service-Roles': roles,
}
header_type = '-Service'
for header_tmplt, attr in six.iteritems(_HEADER_TEMPLATE):
rval[header_tmplt % header_type] = getattr(auth_ref, attr)
return rval
def _invalid_user_token(self, msg=False):
# NOTE(jamielennox): use False as the default so that None is valid
if msg is False:

View File

@ -0,0 +1,180 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import itertools
from oslo_serialization import jsonutils
import six
import webob
def _v3_to_v2_catalog(catalog):
"""Convert a catalog to v2 format.
X_SERVICE_CATALOG must be specified in v2 format. If you get a token
that is in v3 convert it.
"""
v2_services = []
for v3_service in catalog:
# first copy over the entries we allow for the service
v2_service = {'type': v3_service['type']}
try:
v2_service['name'] = v3_service['name']
except KeyError:
pass
# now convert the endpoints. Because in v3 we specify region per
# URL not per group we have to collect all the entries of the same
# region together before adding it to the new service.
regions = {}
for v3_endpoint in v3_service.get('endpoints', []):
region_name = v3_endpoint.get('region')
try:
region = regions[region_name]
except KeyError:
region = {'region': region_name} if region_name else {}
regions[region_name] = region
interface_name = v3_endpoint['interface'].lower() + 'URL'
region[interface_name] = v3_endpoint['url']
v2_service['endpoints'] = list(regions.values())
v2_services.append(v2_service)
return v2_services
class _AuthTokenRequest(webob.Request):
_HEADER_TEMPLATE = {
'X%s-Domain-Id': 'domain_id',
'X%s-Domain-Name': 'domain_name',
'X%s-Project-Id': 'project_id',
'X%s-Project-Name': 'project_name',
'X%s-Project-Domain-Id': 'project_domain_id',
'X%s-Project-Domain-Name': 'project_domain_name',
'X%s-User-Id': 'user_id',
'X%s-User-Name': 'username',
'X%s-User-Domain-Id': 'user_domain_id',
'X%s-User-Domain-Name': 'user_domain_name',
}
_ROLES_TEMPLATE = 'X%s-Roles'
_USER_HEADER_PREFIX = ''
_SERVICE_HEADER_PREFIX = '-Service'
_USER_STATUS_HEADER = 'X-Identity-Status'
_SERVICE_STATUS_HEADER = 'X-Service-Identity-Status'
_SERVICE_CATALOG_HEADER = 'X-Service-Catalog'
_CONFIRMED = 'Confirmed'
_INVALID = 'Invalid'
# header names that have been deprecated in favour of something else.
_DEPRECATED_HEADER_MAP = {
'X-Role': 'X-Roles',
'X-User': 'X-User-Name',
'X-Tenant-Id': 'X-Project-Id',
'X-Tenant-Name': 'X-Project-Name',
'X-Tenant': 'X-Project-Name',
}
def _confirmed(cls, value):
return cls._CONFIRMED if value else cls._INVALID
@property
def user_token_valid(self):
"""User token is marked as valid.
:returns: True if the X-Identity-Status header is set to Confirmed.
:rtype: bool
"""
return self.headers[self._USER_STATUS_HEADER] == self._CONFIRMED
@user_token_valid.setter
def user_token_valid(self, value):
self.headers[self._USER_STATUS_HEADER] = self._confirmed(value)
@property
def service_token_valid(self):
"""Service token is marked as valid.
:returns: True if the X-Service-Identity-Status header
is set to Confirmed.
:rtype: bool
"""
return self.headers[self._SERVICE_STATUS_HEADER] == self._CONFIRMED
@service_token_valid.setter
def service_token_valid(self, value):
self.headers[self._SERVICE_STATUS_HEADER] = self._confirmed(value)
def _set_auth_headers(self, auth_ref, prefix):
names = ','.join(auth_ref.role_names)
self.headers[self._ROLES_TEMPLATE % prefix] = names
for header_tmplt, attr in six.iteritems(self._HEADER_TEMPLATE):
self.headers[header_tmplt % prefix] = getattr(auth_ref, attr)
def set_user_headers(self, auth_ref, include_service_catalog):
"""Convert token object into headers.
Build headers that represent authenticated user - see main
doc info at start of __init__ file for details of headers to be defined
"""
self._set_auth_headers(auth_ref, self._USER_HEADER_PREFIX)
for k, v in six.iteritems(self._DEPRECATED_HEADER_MAP):
self.headers[k] = self.headers[v]
if include_service_catalog and auth_ref.has_service_catalog():
catalog = auth_ref.service_catalog.get_data()
if auth_ref.version == 'v3':
catalog = _v3_to_v2_catalog(catalog)
c = jsonutils.dumps(catalog)
self.headers[self._SERVICE_CATALOG_HEADER] = c
self.user_token_valid = True
def set_service_headers(self, auth_ref):
"""Convert token object into service headers.
Build headers that represent authenticated user - see main
doc info at start of __init__ file for details of headers to be defined
"""
self._set_auth_headers(auth_ref, self._SERVICE_HEADER_PREFIX)
self.service_token_valid = True
def _all_auth_headers(self):
"""All the authentication headers that can be set on the request"""
yield self._SERVICE_CATALOG_HEADER
yield self._USER_STATUS_HEADER
yield self._SERVICE_STATUS_HEADER
for header in self._DEPRECATED_HEADER_MAP:
yield header
prefixes = (self._USER_HEADER_PREFIX, self._SERVICE_HEADER_PREFIX)
for tmpl, prefix in itertools.product(self._HEADER_TEMPLATE, prefixes):
yield tmpl % prefix
for prefix in prefixes:
yield self._ROLES_TEMPLATE % prefix
def remove_auth_headers(self):
"""Remove headers so a user can't fake authentication."""
for header in self._all_auth_headers():
self.headers.pop(header, None)

View File

@ -23,7 +23,6 @@ import time
import uuid
import fixtures
from keystoneclient import access
from keystoneclient import auth
from keystoneclient.common import cms
from keystoneclient import exceptions
@ -1839,69 +1838,6 @@ class v3AuthTokenMiddlewareTest(BaseAuthTokenMiddlewareTest,
self.assertEqual(self.middleware._token_cache.get(token), data)
class CatalogConversionTests(BaseAuthTokenMiddlewareTest):
PUBLIC_URL = 'http://server:5000/v2.0'
ADMIN_URL = 'http://admin:35357/v2.0'
INTERNAL_URL = 'http://internal:5000/v2.0'
REGION_ONE = 'RegionOne'
REGION_TWO = 'RegionTwo'
REGION_THREE = 'RegionThree'
def test_basic_convert(self):
token = fixture.V3Token()
s = token.add_service(type='identity')
s.add_standard_endpoints(public=self.PUBLIC_URL,
admin=self.ADMIN_URL,
internal=self.INTERNAL_URL,
region=self.REGION_ONE)
auth_ref = access.AccessInfo.factory(body=token)
catalog_data = auth_ref.service_catalog.get_data()
catalog = auth_token._v3_to_v2_catalog(catalog_data)
self.assertEqual(1, len(catalog))
service = catalog[0]
self.assertEqual(1, len(service['endpoints']))
endpoints = service['endpoints'][0]
self.assertEqual('identity', service['type'])
self.assertEqual(4, len(endpoints))
self.assertEqual(self.PUBLIC_URL, endpoints['publicURL'])
self.assertEqual(self.ADMIN_URL, endpoints['adminURL'])
self.assertEqual(self.INTERNAL_URL, endpoints['internalURL'])
self.assertEqual(self.REGION_ONE, endpoints['region'])
def test_multi_region(self):
token = fixture.V3Token()
s = token.add_service(type='identity')
s.add_endpoint('internal', self.INTERNAL_URL, region=self.REGION_ONE)
s.add_endpoint('public', self.PUBLIC_URL, region=self.REGION_TWO)
s.add_endpoint('admin', self.ADMIN_URL, region=self.REGION_THREE)
auth_ref = access.AccessInfo.factory(body=token)
catalog_data = auth_ref.service_catalog.get_data()
catalog = auth_token._v3_to_v2_catalog(catalog_data)
self.assertEqual(1, len(catalog))
service = catalog[0]
# the 3 regions will come through as 3 separate endpoints
expected = [{'internalURL': self.INTERNAL_URL,
'region': self.REGION_ONE},
{'publicURL': self.PUBLIC_URL,
'region': self.REGION_TWO},
{'adminURL': self.ADMIN_URL,
'region': self.REGION_THREE}]
self.assertEqual('identity', service['type'])
self.assertEqual(3, len(service['endpoints']))
for e in expected:
self.assertIn(e, expected)
class DelayedAuthTests(BaseAuthTokenMiddlewareTest):
def test_header_in_401(self):

View File

@ -0,0 +1,218 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import itertools
import uuid
from keystoneclient import access
from keystoneclient import fixture
from keystonemiddleware.auth_token import _request
from keystonemiddleware.tests.unit import utils
class RequestObjectTests(utils.TestCase):
def setUp(self):
super(RequestObjectTests, self).setUp()
self.request = _request._AuthTokenRequest.blank('/')
def test_setting_user_token_valid(self):
self.assertNotIn('X-Identity-Status', self.request.headers)
self.request.user_token_valid = True
self.assertEqual('Confirmed',
self.request.headers['X-Identity-Status'])
self.assertTrue(self.request.user_token_valid)
self.request.user_token_valid = False
self.assertEqual('Invalid',
self.request.headers['X-Identity-Status'])
self.assertFalse(self.request.user_token_valid)
def test_setting_service_token_valid(self):
self.assertNotIn('X-Service-Identity-Status', self.request.headers)
self.request.service_token_valid = True
self.assertEqual('Confirmed',
self.request.headers['X-Service-Identity-Status'])
self.assertTrue(self.request.service_token_valid)
self.request.service_token_valid = False
self.assertEqual('Invalid',
self.request.headers['X-Service-Identity-Status'])
self.assertFalse(self.request.service_token_valid)
def test_removing_headers(self):
GOOD = ('X-Auth-Token',
'unknownstring',
uuid.uuid4().hex)
BAD = ('X-Domain-Id',
'X-Domain-Name',
'X-Project-Id',
'X-Project-Name',
'X-Project-Domain-Id',
'X-Project-Domain-Name',
'X-User-Id',
'X-User-Name',
'X-User-Domain-Id',
'X-User-Domain-Name',
'X-Roles',
'X-Identity-Status',
'X-Service-Domain-Id',
'X-Service-Domain-Name',
'X-Service-Project-Id',
'X-Service-Project-Name',
'X-Service-Project-Domain-Id',
'X-Service-Project-Domain-Name',
'X-Service-User-Id',
'X-Service-User-Name',
'X-Service-User-Domain-Id',
'X-Service-User-Domain-Name',
'X-Service-Roles',
'X-Service-Identity-Status',
'X-Service-Catalog',
'X-Role',
'X-User',
'X-Tenant-Id',
'X-Tenant-Name',
'X-Tenant',
)
header_vals = {}
for header in itertools.chain(GOOD, BAD):
v = uuid.uuid4().hex
header_vals[header] = v
self.request.headers[header] = v
self.request.remove_auth_headers()
for header in BAD:
self.assertNotIn(header, self.request.headers)
for header in GOOD:
self.assertEqual(header_vals[header], self.request.headers[header])
def _test_v3_headers(self, token, prefix):
self.assertEqual(token.domain_id,
self.request.headers['X%s-Domain-Id' % prefix])
self.assertEqual(token.domain_name,
self.request.headers['X%s-Domain-Name' % prefix])
self.assertEqual(token.project_id,
self.request.headers['X%s-Project-Id' % prefix])
self.assertEqual(token.project_name,
self.request.headers['X%s-Project-Name' % prefix])
self.assertEqual(
token.project_domain_id,
self.request.headers['X%s-Project-Domain-Id' % prefix])
self.assertEqual(
token.project_domain_name,
self.request.headers['X%s-Project-Domain-Name' % prefix])
self.assertEqual(token.user_id,
self.request.headers['X%s-User-Id' % prefix])
self.assertEqual(token.user_name,
self.request.headers['X%s-User-Name' % prefix])
self.assertEqual(
token.user_domain_id,
self.request.headers['X%s-User-Domain-Id' % prefix])
self.assertEqual(
token.user_domain_name,
self.request.headers['X%s-User-Domain-Name' % prefix])
def test_project_scoped_user_headers(self):
token = fixture.V3Token()
token.set_project_scope()
token_id = uuid.uuid4().hex
auth_ref = access.AccessInfo.factory(token_id=token_id, body=token)
self.request.set_user_headers(auth_ref, include_service_catalog=True)
self._test_v3_headers(token, '')
def test_project_scoped_service_headers(self):
token = fixture.V3Token()
token.set_project_scope()
token_id = uuid.uuid4().hex
auth_ref = access.AccessInfo.factory(token_id=token_id, body=token)
self.request.set_service_headers(auth_ref)
self._test_v3_headers(token, '-Service')
class CatalogConversionTests(utils.TestCase):
PUBLIC_URL = 'http://server:5000/v2.0'
ADMIN_URL = 'http://admin:35357/v2.0'
INTERNAL_URL = 'http://internal:5000/v2.0'
REGION_ONE = 'RegionOne'
REGION_TWO = 'RegionTwo'
REGION_THREE = 'RegionThree'
def test_basic_convert(self):
token = fixture.V3Token()
s = token.add_service(type='identity')
s.add_standard_endpoints(public=self.PUBLIC_URL,
admin=self.ADMIN_URL,
internal=self.INTERNAL_URL,
region=self.REGION_ONE)
auth_ref = access.AccessInfo.factory(body=token)
catalog_data = auth_ref.service_catalog.get_data()
catalog = _request._v3_to_v2_catalog(catalog_data)
self.assertEqual(1, len(catalog))
service = catalog[0]
self.assertEqual(1, len(service['endpoints']))
endpoints = service['endpoints'][0]
self.assertEqual('identity', service['type'])
self.assertEqual(4, len(endpoints))
self.assertEqual(self.PUBLIC_URL, endpoints['publicURL'])
self.assertEqual(self.ADMIN_URL, endpoints['adminURL'])
self.assertEqual(self.INTERNAL_URL, endpoints['internalURL'])
self.assertEqual(self.REGION_ONE, endpoints['region'])
def test_multi_region(self):
token = fixture.V3Token()
s = token.add_service(type='identity')
s.add_endpoint('internal', self.INTERNAL_URL, region=self.REGION_ONE)
s.add_endpoint('public', self.PUBLIC_URL, region=self.REGION_TWO)
s.add_endpoint('admin', self.ADMIN_URL, region=self.REGION_THREE)
auth_ref = access.AccessInfo.factory(body=token)
catalog_data = auth_ref.service_catalog.get_data()
catalog = _request._v3_to_v2_catalog(catalog_data)
self.assertEqual(1, len(catalog))
service = catalog[0]
# the 3 regions will come through as 3 separate endpoints
expected = [{'internalURL': self.INTERNAL_URL,
'region': self.REGION_ONE},
{'publicURL': self.PUBLIC_URL,
'region': self.REGION_TWO},
{'adminURL': self.ADMIN_URL,
'region': self.REGION_THREE}]
self.assertEqual('identity', service['type'])
self.assertEqual(3, len(service['endpoints']))
for e in expected:
self.assertIn(e, expected)