Skip to content

Commit 337d013

Browse files
darosalestevemar
authored andcommitted
Use resource id when name given for identity show
Currently a user is allowed to specify either a resource ID or name when running openstack identity comands. In some cases, when a name is specified instead of an ID, the command will return as not able to find the resource when it in fact does exist. The changes here are to check the client against the token on such requests and to extract the ID of the resource specified if enough information exists between the two. We then use the ID associated with the resource to complete the user requests. Change-Id: I40713b0ded42063b786dc21247e854224b9d2fe2 Closes-Bug: #1561599
1 parent a60f16b commit 337d013

9 files changed

Lines changed: 117 additions & 5 deletions

File tree

openstackclient/identity/common.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,39 @@ def find_service(identity_client, name_type_or_id):
4747
raise exceptions.CommandError(msg % name_type_or_id)
4848

4949

50+
def _get_token_resource(client, resource, parsed_name):
51+
"""Peek into the user's auth token to get resource IDs
52+
53+
Look into a user's token to try and find the ID of a domain, project or
54+
user, when given the name. Typically non-admin users will interact with
55+
the CLI using names. However, by default, keystone does not allow look up
56+
by name since it would involve listing all entities. Instead opt to use
57+
the correct ID (from the token) instead.
58+
:param client: An identity client
59+
:param resource: A resource to look at in the token, this may be `domain`,
60+
`project_domain`, `user_domain`, `project`, or `user`.
61+
:param parsed_name: This is input from parsed_args that the user is hoping
62+
to find in the token.
63+
64+
:returns: The ID of the resource from the token, or the original value from
65+
parsed_args if it does not match.
66+
"""
67+
68+
try:
69+
token = client.auth.client.get_token()
70+
token_data = client.tokens.get_token_data(token)
71+
token_dict = token_data['token']
72+
73+
# NOTE(stevemar): If domain is passed, just look at the project domain.
74+
if resource == 'domain':
75+
token_dict = token_dict['project']
76+
obj = token_dict[resource]
77+
return obj['id'] if obj['name'] == parsed_name else parsed_name
78+
# diaper defense in case parsing the token fails
79+
except Exception: # noqa
80+
return parsed_name
81+
82+
5083
def _get_domain_id_if_requested(identity_client, domain_name_or_id):
5184
if not domain_name_or_id:
5285
return None

openstackclient/identity/v3/domain.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import six
2525

2626
from openstackclient.i18n import _
27+
from openstackclient.identity import common
2728

2829

2930
LOG = logging.getLogger(__name__)
@@ -187,8 +188,12 @@ def get_parser(self, prog_name):
187188

188189
def take_action(self, parsed_args):
189190
identity_client = self.app.client_manager.identity
191+
192+
domain_str = common._get_token_resource(identity_client, 'domain',
193+
parsed_args.domain)
194+
190195
domain = utils.find_resource(identity_client.domains,
191-
parsed_args.domain)
196+
domain_str)
192197

193198
domain._info.pop('links')
194199
return zip(*sorted(six.iteritems(domain._info)))

openstackclient/identity/v3/project.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -321,18 +321,21 @@ def get_parser(self, prog_name):
321321
def take_action(self, parsed_args):
322322
identity_client = self.app.client_manager.identity
323323

324+
project_str = common._get_token_resource(identity_client, 'project',
325+
parsed_args.project)
326+
324327
if parsed_args.domain:
325328
domain = common.find_domain(identity_client, parsed_args.domain)
326329
project = utils.find_resource(
327330
identity_client.projects,
328-
parsed_args.project,
331+
project_str,
329332
domain_id=domain.id,
330333
parents_as_list=parsed_args.parents,
331334
subtree_as_list=parsed_args.children)
332335
else:
333336
project = utils.find_resource(
334337
identity_client.projects,
335-
parsed_args.project,
338+
project_str,
336339
parents_as_list=parsed_args.parents,
337340
subtree_as_list=parsed_args.children)
338341

openstackclient/identity/v3/user.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -444,14 +444,16 @@ def get_parser(self, prog_name):
444444
def take_action(self, parsed_args):
445445
identity_client = self.app.client_manager.identity
446446

447+
user_str = common._get_token_resource(identity_client, 'user',
448+
parsed_args.user)
447449
if parsed_args.domain:
448450
domain = common.find_domain(identity_client, parsed_args.domain)
449451
user = utils.find_resource(identity_client.users,
450-
parsed_args.user,
452+
user_str,
451453
domain_id=domain.id)
452454
else:
453455
user = utils.find_resource(identity_client.users,
454-
parsed_args.user)
456+
user_str)
455457

456458
user._info.pop('links')
457459
return zip(*sorted(six.iteritems(user._info)))

openstackclient/tests/identity/v3/fakes.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -502,6 +502,9 @@ def __init__(self, **kwargs):
502502
self.role_assignments.resource_class = fakes.FakeResource(None, {})
503503
self.auth_token = kwargs['token']
504504
self.management_url = kwargs['endpoint']
505+
self.auth = FakeAuth()
506+
self.auth.client = mock.Mock()
507+
self.auth.client.resource_class = fakes.FakeResource(None, {})
505508

506509

507510
class FakeFederationManager(object):

openstackclient/tests/identity/v3/test_domain.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -389,6 +389,16 @@ def test_domain_show(self):
389389
('domain', identity_fakes.domain_id),
390390
]
391391
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
392+
self.app.client_manager.identity.tokens.get_token_data.return_value = \
393+
{'token':
394+
{'project':
395+
{'domain':
396+
{'id': 'd1',
397+
'name': 'd1'
398+
}
399+
}
400+
}
401+
}
392402

393403
# In base command class ShowOne in cliff, abstract method take_action()
394404
# returns a two-part tuple with a tuple of column names and a tuple of

openstackclient/tests/identity/v3/test_project.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -749,6 +749,7 @@ def setUp(self):
749749
self.cmd = project.ShowProject(self.app, None)
750750

751751
def test_project_show(self):
752+
752753
arglist = [
753754
identity_fakes.project_id,
754755
]
@@ -757,6 +758,16 @@ def test_project_show(self):
757758
]
758759
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
759760

761+
self.app.client_manager.identity.tokens.get_token_data.return_value = \
762+
{'token':
763+
{'project':
764+
{'domain': {},
765+
'name': parsed_args.project,
766+
'id': parsed_args.project
767+
}
768+
}
769+
}
770+
760771
# In base command class ShowOne in cliff, abstract method take_action()
761772
# returns a two-part tuple with a tuple of column names and a tuple of
762773
# data to be shown.
@@ -797,6 +808,15 @@ def test_project_show_parents(self):
797808
('children', False),
798809
]
799810
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
811+
self.app.client_manager.identity.tokens.get_token_data.return_value = \
812+
{'token':
813+
{'project':
814+
{'domain': {},
815+
'name': parsed_args.project,
816+
'id': parsed_args.project
817+
}
818+
}
819+
}
800820

801821
columns, data = self.cmd.take_action(parsed_args)
802822
self.projects_mock.get.assert_called_with(
@@ -845,6 +865,15 @@ def test_project_show_subtree(self):
845865
('children', True),
846866
]
847867
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
868+
self.app.client_manager.identity.tokens.get_token_data.return_value = \
869+
{'token':
870+
{'project':
871+
{'domain': {},
872+
'name': parsed_args.project,
873+
'id': parsed_args.project
874+
}
875+
}
876+
}
848877

849878
columns, data = self.cmd.take_action(parsed_args)
850879
self.projects_mock.get.assert_called_with(
@@ -895,6 +924,15 @@ def test_project_show_parents_and_children(self):
895924
('children', True),
896925
]
897926
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
927+
self.app.client_manager.identity.tokens.get_token_data.return_value = \
928+
{'token':
929+
{'project':
930+
{'domain': {},
931+
'name': parsed_args.project,
932+
'id': parsed_args.project
933+
}
934+
}
935+
}
898936

899937
columns, data = self.cmd.take_action(parsed_args)
900938
self.projects_mock.get.assert_called_with(

openstackclient/tests/identity/v3/test_user.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1094,6 +1094,17 @@ def setUp(self):
10941094

10951095
# Get the command object to test
10961096
self.cmd = user.ShowUser(self.app, None)
1097+
self.app.client_manager.identity.auth.client.get_user_id.\
1098+
return_value = 'bbbbbbb-aaaa-aaaa-aaaa-bbbbbbbaaaa'
1099+
self.app.client_manager.identity.tokens.get_token_data.return_value = \
1100+
{'token':
1101+
{'user':
1102+
{'domain': {},
1103+
'id': 'bbbbbbb-aaaa-aaaa-aaaa-bbbbbbbaaaa',
1104+
'name': 'bbbbbbb-aaaa-aaaa-aaaa-bbbbbbbaaaa'
1105+
}
1106+
}
1107+
}
10971108

10981109
def test_user_show(self):
10991110
arglist = [
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
fixes:
3+
- When performing ``domain show``, ``project show`` or ``user show``, peek
4+
into the user token to determine the ID or the resource (if supplied with
5+
only a name). This should make finding information about the user and
6+
their project easier for non-admin users.
7+
[Bug `1561599 <https://bugs.launchpad.net/bugs/1561599>`_]

0 commit comments

Comments
 (0)