Skip to content

Commit e78c9bc

Browse files
Zuulopenstack-gerrit
authored andcommitted
Merge "Add CRUD support for application credentials"
2 parents 0dfb690 + 375964f commit e78c9bc

8 files changed

Lines changed: 834 additions & 0 deletions

File tree

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
======================
2+
application credential
3+
======================
4+
5+
Identity v3
6+
7+
With application credentials, a user can grant their applications limited
8+
access to their cloud resources. Once created, users can authenticate with an
9+
application credential by using the ``v3applicationcredential`` auth type.
10+
11+
application credential create
12+
-----------------------------
13+
14+
Create new application credential
15+
16+
.. program:: application credential create
17+
.. code:: bash
18+
19+
openstack application credential create
20+
[--secret <secret>]
21+
[--role <role>]
22+
[--expiration <expiration>]
23+
[--description <description>]
24+
[--unrestricted]
25+
<name>
26+
27+
.. option:: --secret <secret>
28+
29+
Secret to use for authentication (if not provided, one will be generated)
30+
31+
.. option:: --role <role>
32+
33+
Roles to authorize (name or ID) (repeat option to set multiple values)
34+
35+
.. option:: --expiration <expiration>
36+
37+
Sets an expiration date for the application credential (format of
38+
YYYY-mm-ddTHH:MM:SS)
39+
40+
.. option:: --description <description>
41+
42+
Application credential description
43+
44+
.. option:: --unrestricted
45+
46+
Enable application credential to create and delete other application
47+
credentials and trusts (this is potentially dangerous behavior and is
48+
disabled by default)
49+
50+
.. option:: --restricted
51+
52+
Prohibit application credential from creating and deleting other
53+
application credentials and trusts (this is the default behavior)
54+
55+
.. describe:: <name>
56+
57+
Name of the application credential
58+
59+
60+
application credential delete
61+
-----------------------------
62+
63+
Delete application credential(s)
64+
65+
.. program:: application credential delete
66+
.. code:: bash
67+
68+
openstack application credential delete
69+
<application-credential> [<application-credential> ...]
70+
71+
.. describe:: <application-credential>
72+
73+
Application credential(s) to delete (name or ID)
74+
75+
application credential list
76+
---------------------------
77+
78+
List application credentials
79+
80+
.. program:: application credential list
81+
.. code:: bash
82+
83+
openstack application credential list
84+
[--user <user>]
85+
[--user-domain <user-domain>]
86+
87+
.. option:: --user
88+
89+
User whose application credentials to list (name or ID)
90+
91+
.. option:: --user-domain
92+
93+
Domain the user belongs to (name or ID). This can be
94+
used in case collisions between user names exist.
95+
96+
application credential show
97+
---------------------------
98+
99+
Display application credential details
100+
101+
.. program:: application credential show
102+
.. code:: bash
103+
104+
openstack application credential show
105+
<application-credential>
106+
107+
.. describe:: <application-credential>
108+
109+
Application credential to display (name or ID)

openstackclient/identity/common.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,13 @@ def _get_token_resource(client, resource, parsed_name, parsed_domain=None):
101101
# user/project under different domain may has a same name
102102
if parsed_domain and parsed_domain not in obj['domain'].values():
103103
return parsed_name
104+
if isinstance(obj, list):
105+
for item in obj:
106+
if item['name'] == parsed_name:
107+
return item['id']
108+
if item['id'] == parsed_name:
109+
return parsed_name
110+
return parsed_name
104111
return obj['id'] if obj['name'] == parsed_name else parsed_name
105112
# diaper defense in case parsing the token fails
106113
except Exception: # noqa
Lines changed: 220 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,220 @@
1+
# Copyright 2018 SUSE Linux GmbH
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License"); you may
4+
# not use this file except in compliance with the License. You may obtain
5+
# a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11+
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12+
# License for the specific language governing permissions and limitations
13+
# under the License.
14+
#
15+
16+
"""Identity v3 Application Credential action implementations"""
17+
18+
import datetime
19+
import logging
20+
21+
from osc_lib.command import command
22+
from osc_lib import exceptions
23+
from osc_lib import utils
24+
import six
25+
26+
from openstackclient.i18n import _
27+
from openstackclient.identity import common
28+
29+
30+
LOG = logging.getLogger(__name__)
31+
32+
33+
class CreateApplicationCredential(command.ShowOne):
34+
_description = _("Create new application credential")
35+
36+
def get_parser(self, prog_name):
37+
parser = super(CreateApplicationCredential, self).get_parser(prog_name)
38+
parser.add_argument(
39+
'name',
40+
metavar='<name>',
41+
help=_('Name of the application credential'),
42+
)
43+
parser.add_argument(
44+
'--secret',
45+
metavar='<secret>',
46+
help=_('Secret to use for authentication (if not provided, one'
47+
' will be generated)'),
48+
)
49+
parser.add_argument(
50+
'--role',
51+
metavar='<role>',
52+
action='append',
53+
default=[],
54+
help=_('Roles to authorize (name or ID) (repeat option to set'
55+
' multiple values)'),
56+
)
57+
parser.add_argument(
58+
'--expiration',
59+
metavar='<expiration>',
60+
help=_('Sets an expiration date for the application credential,'
61+
' format of YYYY-mm-ddTHH:MM:SS (if not provided, the'
62+
' application credential will not expire)'),
63+
)
64+
parser.add_argument(
65+
'--description',
66+
metavar='<description>',
67+
help=_('Application credential description'),
68+
)
69+
parser.add_argument(
70+
'--unrestricted',
71+
action="store_true",
72+
help=_('Enable application credential to create and delete other'
73+
' application credentials and trusts (this is potentially'
74+
' dangerous behavior and is disabled by default)'),
75+
)
76+
parser.add_argument(
77+
'--restricted',
78+
action="store_true",
79+
help=_('Prohibit application credential from creating and deleting'
80+
' other application credentials and trusts (this is the'
81+
' default behavior)'),
82+
)
83+
return parser
84+
85+
def take_action(self, parsed_args):
86+
identity_client = self.app.client_manager.identity
87+
88+
role_ids = []
89+
for role in parsed_args.role:
90+
# A user can only create an application credential for themself,
91+
# not for another user even as an admin, and only on the project to
92+
# which they are currently scoped with a subset of the role
93+
# assignments they have on that project. Don't bother trying to
94+
# look up roles via keystone, just introspect the token.
95+
role_id = common._get_token_resource(identity_client, "roles",
96+
role)
97+
role_ids.append(role_id)
98+
99+
expires_at = None
100+
if parsed_args.expiration:
101+
expires_at = datetime.datetime.strptime(parsed_args.expiration,
102+
'%Y-%m-%dT%H:%M:%S')
103+
104+
if parsed_args.restricted:
105+
unrestricted = False
106+
else:
107+
unrestricted = parsed_args.unrestricted
108+
109+
app_cred_manager = identity_client.application_credentials
110+
application_credential = app_cred_manager.create(
111+
parsed_args.name,
112+
roles=role_ids,
113+
expires_at=expires_at,
114+
description=parsed_args.description,
115+
secret=parsed_args.secret,
116+
unrestricted=unrestricted,
117+
)
118+
119+
application_credential._info.pop('links', None)
120+
121+
# Format roles into something sensible
122+
roles = application_credential._info.pop('roles')
123+
msg = ' '.join(r['name'] for r in roles)
124+
application_credential._info['roles'] = msg
125+
126+
return zip(*sorted(six.iteritems(application_credential._info)))
127+
128+
129+
class DeleteApplicationCredential(command.Command):
130+
_description = _("Delete application credentials(s)")
131+
132+
def get_parser(self, prog_name):
133+
parser = super(DeleteApplicationCredential, self).get_parser(prog_name)
134+
parser.add_argument(
135+
'application_credential',
136+
metavar='<application-credential>',
137+
nargs="+",
138+
help=_('Application credentials(s) to delete (name or ID)'),
139+
)
140+
return parser
141+
142+
def take_action(self, parsed_args):
143+
identity_client = self.app.client_manager.identity
144+
145+
errors = 0
146+
for ac in parsed_args.application_credential:
147+
try:
148+
app_cred = utils.find_resource(
149+
identity_client.application_credentials, ac)
150+
identity_client.application_credentials.delete(app_cred.id)
151+
except Exception as e:
152+
errors += 1
153+
LOG.error(_("Failed to delete application credential with "
154+
"name or ID '%(ac)s': %(e)s"),
155+
{'ac': ac, 'e': e})
156+
157+
if errors > 0:
158+
total = len(parsed_args.application_credential)
159+
msg = (_("%(errors)s of %(total)s application credentials failed "
160+
"to delete.") % {'errors': errors, 'total': total})
161+
raise exceptions.CommandError(msg)
162+
163+
164+
class ListApplicationCredential(command.Lister):
165+
_description = _("List application credentials")
166+
167+
def get_parser(self, prog_name):
168+
parser = super(ListApplicationCredential, self).get_parser(prog_name)
169+
parser.add_argument(
170+
'--user',
171+
metavar='<user>',
172+
help=_('User whose application credentials to list (name or ID)'),
173+
)
174+
common.add_user_domain_option_to_parser(parser)
175+
return parser
176+
177+
def take_action(self, parsed_args):
178+
identity_client = self.app.client_manager.identity
179+
if parsed_args.user:
180+
user_id = common.find_user(identity_client,
181+
parsed_args.user,
182+
parsed_args.user_domain).id
183+
else:
184+
user_id = None
185+
186+
columns = ('ID', 'Name', 'Project ID', 'Description', 'Expires At')
187+
data = identity_client.application_credentials.list(
188+
user=user_id)
189+
return (columns,
190+
(utils.get_item_properties(
191+
s, columns,
192+
formatters={},
193+
) for s in data))
194+
195+
196+
class ShowApplicationCredential(command.ShowOne):
197+
_description = _("Display application credential details")
198+
199+
def get_parser(self, prog_name):
200+
parser = super(ShowApplicationCredential, self).get_parser(prog_name)
201+
parser.add_argument(
202+
'application_credential',
203+
metavar='<application-credential>',
204+
help=_('Application credential to display (name or ID)'),
205+
)
206+
return parser
207+
208+
def take_action(self, parsed_args):
209+
identity_client = self.app.client_manager.identity
210+
app_cred = utils.find_resource(identity_client.application_credentials,
211+
parsed_args.application_credential)
212+
213+
app_cred._info.pop('links', None)
214+
215+
# Format roles into something sensible
216+
roles = app_cred._info.pop('roles')
217+
msg = ' '.join(r['name'] for r in roles)
218+
app_cred._info['roles'] = msg
219+
220+
return zip(*sorted(six.iteritems(app_cred._info)))

0 commit comments

Comments
 (0)