Adds support for username to match the v2 spec

The v2.0 API spec uses username for the user's name, whereas the v3 API
just uses name. The v2.0 implementaion incorrectly used name instead of
username, but did allow a username to be specified and stored in the
extras.

This patch makes the implementation more closely conform to the API
without breaking backward compatibility. Anyone using name in the v2.0
API can continue to do so. They can even specify a username that will
still get stored in the extras. Users can now use the documented
username instead of name and the API will work for them as well.

Both name and username will always be returned for the v2 API calls.

DocImpact
Change-Id: Ia95aa5d442a8311925399fa59e5022d31f68d374
Closes-Bug: #1214686
This commit is contained in:
David Stanek 2013-09-25 13:29:33 +00:00
parent 607b850e8f
commit 6f8bdae1db
5 changed files with 186 additions and 1 deletions

View File

@ -313,6 +313,30 @@ class V2Controller(wsgi.Application):
ref.pop('domain_id', None)
return ref
@staticmethod
def normalize_username_in_response(ref):
"""Adds username to outgoing user refs to match the v2 spec.
Internally we use `name` to represent a user's name. The v2 spec
requires the use of `username` instead.
"""
if 'username' not in ref and 'name' in ref:
ref['username'] = ref['name']
return ref
@staticmethod
def normalize_username_in_request(ref):
"""Adds name in incoming user refs to match the v2 spec.
Internally we use `name` to represent a user's name. The v2 spec
requires the use of `username` instead.
"""
if 'name' not in ref and 'username' in ref:
ref['name'] = ref.pop('username')
return ref
class V3Controller(V2Controller):
"""Base controller class for Identity API v3.

View File

@ -192,6 +192,7 @@ class User(controller.V2Controller):
# CRUD extension
def create_user(self, context, user):
user = self._normalize_OSKSADM_password_on_request(user)
user = self.normalize_username_in_request(user)
user = self._normalize_dict(user)
self.assert_admin(context)
@ -221,6 +222,7 @@ class User(controller.V2Controller):
def update_user(self, context, user_id, user):
# NOTE(termie): this is really more of a patch than a put
user = self.normalize_username_in_request(user)
self.assert_admin(context)
if 'enabled' in user and not isinstance(user['enabled'], bool):

View File

@ -213,6 +213,7 @@ class Manager(manager.Manager):
* v2.0 users are not domain aware, and should have domain_id removed
* v2.0 users expect the use of tenantId instead of default_project_id
* v2.0 users have a username attribute
This method should only be applied to user_refs being returned from the
v2.0 controller(s).
@ -237,6 +238,7 @@ class Manager(manager.Manager):
"""Run through the various filter/normalization methods."""
_format_default_project_id(ref)
controller.V2Controller.filter_domain_id(ref)
controller.V2Controller.normalize_username_in_response(ref)
return ref
if isinstance(ref, dict):

View File

@ -783,6 +783,161 @@ class LegacyV2UsernameTests(object):
user = self.get_user_from_response(r)
self.assertEqual(user.get('username'), 'new_username')
def test_username_is_always_returned_create(self):
"""Username is set as the value of name if no username is provided.
This matches the v2.0 spec where we really should be using username
and not name.
"""
r = self.create_user()
self.assertValidUserResponse(r)
user = self.get_user_from_response(r)
self.assertEqual(user.get('name'), user.get('username'))
def test_username_is_always_returned_get(self):
"""Username is set as the value of name if no username is provided.
This matches the v2.0 spec where we really should be using username
and not name.
"""
token = self.get_scoped_token()
r = self.create_user()
id_ = self.get_user_attribute_from_response(r, 'id')
r = self.admin_request(path='/v2.0/users/%s' % id_, token=token)
self.assertValidUserResponse(r)
user = self.get_user_from_response(r)
self.assertEqual(user.get('name'), user.get('username'))
def test_username_is_always_returned_get_by_name(self):
"""Username is set as the value of name if no username is provided.
This matches the v2.0 spec where we really should be using username
and not name.
"""
token = self.get_scoped_token()
r = self.create_user()
name = self.get_user_attribute_from_response(r, 'name')
r = self.admin_request(path='/v2.0/users?name=%s' % name, token=token)
self.assertValidUserResponse(r)
user = self.get_user_from_response(r)
self.assertEqual(user.get('name'), user.get('username'))
def test_username_is_always_returned_update_no_username_provided(self):
"""Username is set as the value of name if no username is provided.
This matches the v2.0 spec where we really should be using username
and not name.
"""
token = self.get_scoped_token()
r = self.create_user()
id_ = self.get_user_attribute_from_response(r, 'id')
name = self.get_user_attribute_from_response(r, 'name')
enabled = self.get_user_attribute_from_response(r, 'enabled')
r = self.admin_request(
method='PUT',
path='/v2.0/users/%s' % id_,
token=token,
body={
'user': {
'name': name,
'enabled': enabled,
},
},
expected_status=200)
self.assertValidUserResponse(r)
user = self.get_user_from_response(r)
self.assertEqual(user.get('name'), user.get('username'))
def test_updated_username_is_returned(self):
"""Username is set as the value of name if no username is provided.
This matches the v2.0 spec where we really should be using username
and not name.
"""
token = self.get_scoped_token()
r = self.create_user()
id_ = self.get_user_attribute_from_response(r, 'id')
name = self.get_user_attribute_from_response(r, 'name')
enabled = self.get_user_attribute_from_response(r, 'enabled')
r = self.admin_request(
method='PUT',
path='/v2.0/users/%s' % id_,
token=token,
body={
'user': {
'name': name,
'enabled': enabled,
},
},
expected_status=200)
self.assertValidUserResponse(r)
user = self.get_user_from_response(r)
self.assertEqual(user.get('name'), user.get('username'))
def test_username_can_be_used_instead_of_name_create(self):
token = self.get_scoped_token()
r = self.admin_request(
method='POST',
path='/v2.0/users',
token=token,
body={
'user': {
'username': uuid.uuid4().hex,
'enabled': True,
},
},
expected_status=200)
self.assertValidUserResponse(r)
user = self.get_user_from_response(r)
self.assertEqual(user.get('name'), user.get('username'))
def test_username_can_be_used_instead_of_name_update(self):
token = self.get_scoped_token()
r = self.create_user()
id_ = self.get_user_attribute_from_response(r, 'id')
new_username = uuid.uuid4().hex
enabled = self.get_user_attribute_from_response(r, 'enabled')
r = self.admin_request(
method='PUT',
path='/v2.0/users/%s' % id_,
token=token,
body={
'user': {
'username': new_username,
'enabled': enabled,
},
},
expected_status=200)
self.assertValidUserResponse(r)
user = self.get_user_from_response(r)
self.assertEqual(user.get('name'), new_username)
self.assertEqual(user.get('name'), user.get('username'))
class RestfulTestCase(rest.RestfulTestCase):

View File

@ -1623,11 +1623,13 @@ class TestV3toV2Methods(tests.TestCase):
# Expected result if the user is meant to have a tenantId element
self.expected_user = {'id': self.user_id,
'name': self.user_id,
'username': self.user_id,
'tenantId': self.default_project_id}
# Expected result if the user is not meant ot have a tenantId element
self.expected_user_no_tenant_id = {'id': self.user_id,
'name': self.user_id}
'name': self.user_id,
'username': self.user_id}
def test_v3_to_v2_user_method(self):