Add WSGI environment to context

The environment dictionary contains an unspecified set of variables
that contain information about the authentication and authorization
processes.  Not all of the values are known ahead of time.  The
two values for Kerberos (REMOTE_USER, AUTH_TYPE) are a subset.

Instead of making the WSGI layer know about a growing superset of
authentication attributes, the context contains a link to the wsgi
environment.  The environment is removed from the request dictionary
to prevent circular references and potential GC issues.

Fixed tests  to set environment in the context.  While this
changed many tests, the alternative was to make the check
for the environment optional in the controllers.  This is not a
realistic way to test.  If context['environment'] it missing, a test
will trigger a key_error.

Closes-Bug: #1241812

Change-Id: I234677547204e9ddc0ab33db3e6aa8b7d959a01a
This commit is contained in:
Adam Young 2013-10-18 21:40:37 -04:00
parent 08b9ef10fc
commit 923b90ef8c
9 changed files with 50 additions and 41 deletions

View File

@ -332,7 +332,7 @@ class Auth(controller.V3Controller):
"""Authenticate user."""
# user has been authenticated externally
if 'REMOTE_USER' in context:
if 'REMOTE_USER' in context['environment']:
external = get_auth_method('external')
external.authenticate(context, auth_info, auth_context)

View File

@ -40,7 +40,7 @@ class Base(auth.AuthMethodHandler):
user_id from the actual user from the REMOTE_USER env variable.
"""
try:
REMOTE_USER = context['REMOTE_USER']
REMOTE_USER = context['environment']['REMOTE_USER']
except KeyError:
msg = _('No authenticated user')
raise exception.Unauthorized(msg)
@ -48,7 +48,8 @@ class Base(auth.AuthMethodHandler):
user_ref = self._authenticate(REMOTE_USER, auth_info)
auth_context['user_id'] = user_ref['id']
if ('kerberos' in CONF.token.bind and
context.get('AUTH_TYPE', '').lower() == 'negotiate'):
(context['environment'].get('AUTH_TYPE', '').lower()
== 'negotiate')):
auth_context['bind']['kerberos'] = user_ref['name']
except Exception:
msg = _('Unable to lookup user %s') % (REMOTE_USER)

View File

@ -102,11 +102,12 @@ def validate_token_bind(context, token_ref):
for bind_type, identifier in bind.iteritems():
if bind_type == 'kerberos':
if not context.get('AUTH_TYPE', '').lower() == 'negotiate':
if not (context['environment'].get('AUTH_TYPE', '').lower()
== 'negotiate'):
LOG.info(_("Kerberos credentials required and not present"))
raise exception.Unauthorized()
if not context.get('REMOTE_USER') == identifier:
if not context['environment'].get('REMOTE_USER') == identifier:
LOG.info(_("Kerberos credentials do not match those in bind"))
raise exception.Unauthorized()
@ -214,15 +215,11 @@ class Application(BaseApplication):
context['headers'] = dict(req.headers.iteritems())
context['path'] = req.environ['PATH_INFO']
params = req.environ.get(PARAMS_ENV, {})
for name in ['REMOTE_USER', 'AUTH_TYPE']:
try:
context[name] = req.environ[name]
except KeyError:
try:
del context[name]
except KeyError:
pass
#authentication and authorization attributes are set as environment
#values by the container and processed by the pipeline. the complete
#set is not yet know.
context['environment'] = req.environ
req.environ = None
params.update(arg_dict)

View File

@ -71,6 +71,10 @@ class AuthTest(tests.TestCase):
# need to register the token provider first because auth controller
# depends on it
token.provider.Manager()
self.context_with_remote_user = {'environment':
{'REMOTE_USER': 'FOO',
'AUTH_TYPE': 'Negotiate'}}
self.empty_context = {'environment': {}}
self.controller = token.controllers.Auth()
@ -379,8 +383,8 @@ class AuthWithToken(AuthTest):
def test_token_auth_with_binding(self):
CONF.token.bind = ['kerberos']
body_dict = _build_user_auth()
context = {'REMOTE_USER': 'FOO', 'AUTH_TYPE': 'Negotiate'}
unscoped_token = self.controller.authenticate(context, body_dict)
unscoped_token = self.controller.authenticate(
self.context_with_remote_user, body_dict)
# the token should have bind information in it
bind = unscoped_token['access']['token']['bind']
@ -394,10 +398,11 @@ class AuthWithToken(AuthTest):
self.assertRaises(
exception.Unauthorized,
self.controller.authenticate,
{}, body_dict)
self.empty_context, body_dict)
# using token with remote user context succeeds
scoped_token = self.controller.authenticate(context, body_dict)
scoped_token = self.controller.authenticate(
self.context_with_remote_user, body_dict)
# the bind information should be carried over from the original token
bind = scoped_token['access']['token']['bind']
@ -517,7 +522,7 @@ class AuthWithRemoteUser(AuthTest):
body_dict = _build_user_auth()
remote_token = self.controller.authenticate(
{'REMOTE_USER': 'FOO'}, body_dict)
self.context_with_remote_user, body_dict)
self.assertEqualTokens(local_token, remote_token)
@ -541,7 +546,7 @@ class AuthWithRemoteUser(AuthTest):
body_dict = _build_user_auth(
tenant_name='BAR')
remote_token = self.controller.authenticate(
{'REMOTE_USER': 'FOO'}, body_dict)
self.context_with_remote_user, body_dict)
self.assertEqualTokens(local_token, remote_token)
@ -556,7 +561,7 @@ class AuthWithRemoteUser(AuthTest):
body_dict = _build_user_auth(tenant_name='BAZ')
remote_token = self.controller.authenticate(
{'REMOTE_USER': 'TWO'}, body_dict)
{'environment': {'REMOTE_USER': 'TWO'}}, body_dict)
self.assertEqualTokens(local_token, remote_token)
@ -566,21 +571,21 @@ class AuthWithRemoteUser(AuthTest):
self.assertRaises(
exception.Unauthorized,
self.controller.authenticate,
{'REMOTE_USER': uuid.uuid4().hex},
{'environment': {'REMOTE_USER': uuid.uuid4().hex}},
body_dict)
def test_bind_with_kerberos(self):
CONF.token.bind = ['kerberos']
kerb = {'REMOTE_USER': 'FOO', 'AUTH_TYPE': 'Negotiate'}
body_dict = _build_user_auth(tenant_name="BAR")
token = self.controller.authenticate(kerb, body_dict)
token = self.controller.authenticate(self.context_with_remote_user,
body_dict)
self.assertEqual(token['access']['token']['bind']['kerberos'], 'FOO')
def test_bind_without_config_opt(self):
CONF.token.bind = ['x509']
kerb = {'REMOTE_USER': 'FOO', 'AUTH_TYPE': 'Negotiate'}
body_dict = _build_user_auth(tenant_name='BAR')
token = self.controller.authenticate(kerb, body_dict)
token = self.controller.authenticate(self.context_with_remote_user,
body_dict)
self.assertNotIn('bind', token['access']['token'])
@ -715,7 +720,9 @@ class AuthWithTrust(AuthTest):
'project': {
'id': self.tenant_baz['id']}}}
auth_response = (self.auth_v3_controller.authenticate_for_token
({'query_string': {}}, v3_password_data))
({'environment': {},
'query_string': {}},
v3_password_data))
token = auth_response.headers['X-Subject-Token']
v3_req_with_trust = {
@ -725,7 +732,9 @@ class AuthWithTrust(AuthTest):
"scope": {
"OS-TRUST:trust": {"id": self.new_trust['id']}}}
token_auth_response = (self.auth_v3_controller.authenticate_for_token
({'query_string': {}}, v3_req_with_trust))
({'environment': {},
'query_string': {}},
v3_req_with_trust))
return token_auth_response
def test_create_v3_token_from_trust(self):
@ -754,7 +763,8 @@ class AuthWithTrust(AuthTest):
self.assertRaises(
exception.Forbidden,
self.auth_v3_controller.authenticate_for_token,
{'query_string': {}}, v3_token_data)
{'environment': {},
'query_string': {}}, v3_token_data)
def test_token_from_trust(self):
auth_response = self.fetch_v2_token_from_trust()

View File

@ -74,7 +74,7 @@ class TestAuthPlugin(tests.TestCase):
auth_info = auth.controllers.AuthInfo(None, auth_data)
auth_context = {'extras': {}, 'method_names': []}
try:
self.api.authenticate({}, auth_info, auth_context)
self.api.authenticate({'environment': {}}, auth_info, auth_context)
except exception.AdditionalAuthRequired as e:
self.assertTrue('methods' in e.authentication)
self.assertTrue(METHOD_NAME in e.authentication['methods'])
@ -88,7 +88,7 @@ class TestAuthPlugin(tests.TestCase):
auth_data = {'identity': auth_data}
auth_info = auth.controllers.AuthInfo(None, auth_data)
auth_context = {'extras': {}, 'method_names': []}
self.api.authenticate({}, auth_info, auth_context)
self.api.authenticate({'environment': {}}, auth_info, auth_context)
self.assertEqual(auth_context['user_id'], DEMO_USER_ID)
# test incorrect response
@ -100,6 +100,6 @@ class TestAuthPlugin(tests.TestCase):
auth_context = {'extras': {}, 'method_names': []}
self.assertRaises(exception.Unauthorized,
self.api.authenticate,
{},
{'environment': {}},
auth_info,
auth_context)

View File

@ -49,12 +49,12 @@ class BindTest(tests.TestCase):
self.assert_kerberos_bind(tokens, bind_level,
use_kerberos=val, success=success)
else:
context = {}
context = {'environment': {}}
CONF.token.enforce_token_bind = bind_level
if use_kerberos:
context['REMOTE_USER'] = KERBEROS_BIND
context['AUTH_TYPE'] = 'Negotiate'
context['environment']['REMOTE_USER'] = KERBEROS_BIND
context['environment']['AUTH_TYPE'] = 'Negotiate'
if not success:
self.assertRaises(exception.Unauthorized,

View File

@ -158,6 +158,7 @@ class RestfulTestCase(rest.RestfulTestCase):
self.public_server = self.serveapp(app_conf, name='main')
self.admin_server = self.serveapp(app_conf, name='admin')
self.empty_context = {'environment': {}}
def tearDown(self):
self.public_server.kill()
@ -1039,7 +1040,7 @@ class RestfulTestCase(rest.RestfulTestCase):
return {'auth': auth_data}
def build_external_auth_request(self, remote_user, auth_data=None):
context = {'REMOTE_USER': remote_user}
context = {'environment': {'REMOTE_USER': remote_user}}
if not auth_data:
auth_data = self.build_authentication_request()['auth']
no_context = None

View File

@ -1666,12 +1666,11 @@ class TestAuthJSON(test_v3.RestfulTestCase):
auth_data['identity']['methods'] = ["password", "external"]
auth_data['identity']['external'] = {}
api = auth.controllers.Auth()
context = {}
auth_info = auth.controllers.AuthInfo(None, auth_data)
auth_context = {'extras': {}, 'method_names': []}
self.assertRaises(exception.Unauthorized,
api.authenticate,
context,
self.empty_context,
auth_info,
auth_context)

View File

@ -279,7 +279,7 @@ class Auth(controller.V2Controller):
Returns auth_token_data, (user_ref, tenant_ref, metadata_ref)
"""
if 'REMOTE_USER' not in context:
if 'REMOTE_USER' not in context.get('environment', {}):
raise ExternalAuthNotApplicable()
#NOTE(jamielennox): xml and json differ and get confused about what
@ -287,7 +287,7 @@ class Auth(controller.V2Controller):
if not auth:
auth = {}
username = context['REMOTE_USER']
username = context['environment']['REMOTE_USER']
try:
user_ref = self.identity_api.get_user_by_name(
username, DEFAULT_DOMAIN_ID)
@ -303,7 +303,8 @@ class Auth(controller.V2Controller):
expiry = core.default_expire_time()
bind = None
if ('kerberos' in CONF.token.bind and
context.get('AUTH_TYPE', '').lower() == 'negotiate'):
context['environment'].
get('AUTH_TYPE', '').lower() == 'negotiate'):
bind = {'kerberos': username}
return (user_ref, tenant_ref, metadata_ref, expiry, bind)