Skip to content

Commit 6ae0d2e

Browse files
navidepalvarolopez
authored andcommitted
Moving authentication from keystoneclient to keystoneauth
Currently OpenStackClient uses keystoneclient for authentication. This change will update OpenStackClient to use keystoneauth for authentication. All dependant test have been updated. Updating how auth_ref is set in the tests to use KSA fixtures had some racy side-effects. The user_role_list tests failed when they picked up an auth_ref that was a fixture. This exposed a weakness in ListUserRole that needed to be fixed at the same time re handling of unscoped tokens and options. Change-Id: I4ddb2dbbb3bf2ab37494468eaf65cef9213a6e00 Closes-Bug: 1533369
1 parent ada6abb commit 6ae0d2e

18 files changed

Lines changed: 330 additions & 146 deletions

File tree

openstackclient/api/auth.py

Lines changed: 11 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,12 @@
1616
import argparse
1717
import logging
1818

19-
import stevedore
20-
21-
from keystoneclient.auth import base
19+
from keystoneauth1.loading import base
2220

2321
from openstackclient.common import exceptions as exc
2422
from openstackclient.common import utils
2523
from openstackclient.i18n import _
2624

27-
2825
LOG = logging.getLogger(__name__)
2926

3027
# Initialize the list of Authentication plugins early in order
@@ -37,15 +34,10 @@
3734

3835
def get_plugin_list():
3936
"""Gather plugin list and cache it"""
40-
4137
global PLUGIN_LIST
4238

4339
if PLUGIN_LIST is None:
44-
PLUGIN_LIST = stevedore.ExtensionManager(
45-
base.PLUGIN_NAMESPACE,
46-
invoke_on_load=False,
47-
propagate_map_exceptions=True,
48-
)
40+
PLUGIN_LIST = base.get_available_plugin_names()
4941
return PLUGIN_LIST
5042

5143

@@ -55,8 +47,9 @@ def get_options_list():
5547
global OPTIONS_LIST
5648

5749
if not OPTIONS_LIST:
58-
for plugin in get_plugin_list():
59-
for o in plugin.plugin.get_options():
50+
for plugin_name in get_plugin_list():
51+
plugin_options = base.get_plugin_options(plugin_name)
52+
for o in plugin_options:
6053
os_name = o.dest.lower().replace('_', '-')
6154
os_env_name = 'OS_' + os_name.upper().replace('-', '_')
6255
OPTIONS_LIST.setdefault(
@@ -66,7 +59,7 @@ def get_options_list():
6659
# help texts if they vary from one auth plugin to another
6760
# also the text rendering is ugly in the CLI ...
6861
OPTIONS_LIST[os_name]['help'] += 'With %s: %s\n' % (
69-
plugin.name,
62+
plugin_name,
7063
o.help,
7164
)
7265
return OPTIONS_LIST
@@ -83,7 +76,7 @@ def select_auth_plugin(options):
8376
if options.auth.get('url') and options.auth.get('token'):
8477
# service token authentication
8578
auth_plugin_name = 'token_endpoint'
86-
elif options.auth_type in [plugin.name for plugin in PLUGIN_LIST]:
79+
elif options.auth_type in PLUGIN_LIST:
8780
# A direct plugin name was given, use it
8881
auth_plugin_name = options.auth_type
8982
elif options.auth.get('username'):
@@ -115,7 +108,7 @@ def build_auth_params(auth_plugin_name, cmd_options):
115108
auth_params = dict(cmd_options.auth)
116109
if auth_plugin_name:
117110
LOG.debug('auth_type: %s', auth_plugin_name)
118-
auth_plugin_class = base.get_plugin_class(auth_plugin_name)
111+
auth_plugin_loader = base.get_plugin_loader(auth_plugin_name)
119112
# grab tenant from project for v2.0 API compatibility
120113
if auth_plugin_name.startswith("v2"):
121114
if 'project_id' in auth_params:
@@ -127,12 +120,12 @@ def build_auth_params(auth_plugin_name, cmd_options):
127120
else:
128121
LOG.debug('no auth_type')
129122
# delay the plugin choice, grab every option
130-
auth_plugin_class = None
123+
auth_plugin_loader = None
131124
plugin_options = set([o.replace('-', '_') for o in get_options_list()])
132125
for option in plugin_options:
133126
LOG.debug('fetching option %s', option)
134127
auth_params[option] = getattr(cmd_options.auth, option, None)
135-
return (auth_plugin_class, auth_params)
128+
return (auth_plugin_loader, auth_params)
136129

137130

138131
def check_valid_auth_options(options, auth_plugin_name, required_scope=True):
@@ -188,7 +181,7 @@ def build_auth_plugins_option_parser(parser):
188181
authentication plugin.
189182
190183
"""
191-
available_plugins = [plugin.name for plugin in get_plugin_list()]
184+
available_plugins = list(get_plugin_list())
192185
parser.add_argument(
193186
'--os-auth-type',
194187
metavar='<auth-type>',

openstackclient/api/auth_plugin.py

Lines changed: 7 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,13 @@
1818
from oslo_config import cfg
1919
from six.moves.urllib import parse as urlparse
2020

21-
from keystoneclient.auth.identity.generic import password as ksc_password
22-
from keystoneclient.auth import token_endpoint
21+
from keystoneauth1.loading._plugins import admin_token as token_endpoint
22+
from keystoneauth1.loading._plugins.identity import generic as ksa_password
2323

2424
LOG = logging.getLogger(__name__)
2525

2626

27-
class TokenEndpoint(token_endpoint.Token):
27+
class TokenEndpoint(token_endpoint.AdminToken):
2828
"""Auth plugin to handle traditional token/endpoint usage
2929
3030
Implements the methods required to handle token authentication
@@ -36,20 +36,15 @@ class TokenEndpoint(token_endpoint.Token):
3636
is for bootstrapping the Keystone database.
3737
"""
3838

39-
def __init__(self, url, token, **kwargs):
39+
def load_from_options(self, url, token):
4040
"""A plugin for static authentication with an existing token
4141
4242
:param string url: Service endpoint
4343
:param string token: Existing token
4444
"""
45-
super(TokenEndpoint, self).__init__(endpoint=url,
46-
token=token)
45+
return super(TokenEndpoint, self).load_from_options(endpoint=url,
46+
token=token)
4747

48-
def get_auth_ref(self, session, **kwargs):
49-
# Stub this method for compatibility
50-
return None
51-
52-
@classmethod
5348
def get_options(self):
5449
options = super(TokenEndpoint, self).get_options()
5550

@@ -65,7 +60,7 @@ def get_options(self):
6560
return options
6661

6762

68-
class OSCGenericPassword(ksc_password.Password):
63+
class OSCGenericPassword(ksa_password.Password):
6964
"""Auth plugin hack to work around broken Keystone configurations
7065
7166
The default Keystone configuration uses http://localhost:xxxx in

openstackclient/common/clientmanager.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -269,7 +269,7 @@ def get_endpoint_for_service_type(self, service_type, region_name=None,
269269
endpoint = self.auth_ref.service_catalog.url_for(
270270
service_type=service_type,
271271
region_name=region_name,
272-
endpoint_type=interface,
272+
interface=interface,
273273
)
274274
else:
275275
# Get the passed endpoint directly from the auth plugin

openstackclient/identity/v2_0/catalog.py

Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
import six
1717

1818
from openstackclient.common import command
19+
from openstackclient.common import exceptions
1920
from openstackclient.common import utils
2021
from openstackclient.i18n import _
2122

@@ -41,13 +42,14 @@ class ListCatalog(command.Lister):
4142

4243
def take_action(self, parsed_args):
4344

44-
# This is ugly because if auth hasn't happened yet we need
45-
# to trigger it here.
46-
sc = self.app.client_manager.session.auth.get_auth_ref(
47-
self.app.client_manager.session,
48-
).service_catalog
45+
# Trigger auth if it has not happened yet
46+
auth_ref = self.app.client_manager.auth_ref
47+
if not auth_ref:
48+
raise exceptions.AuthorizationFailure(
49+
"Only an authorized user may issue a new token."
50+
)
4951

50-
data = sc.get_data()
52+
data = auth_ref.service_catalog.catalog
5153
columns = ('Name', 'Type', 'Endpoints')
5254
return (columns,
5355
(utils.get_dict_properties(
@@ -72,14 +74,15 @@ def get_parser(self, prog_name):
7274

7375
def take_action(self, parsed_args):
7476

75-
# This is ugly because if auth hasn't happened yet we need
76-
# to trigger it here.
77-
sc = self.app.client_manager.session.auth.get_auth_ref(
78-
self.app.client_manager.session,
79-
).service_catalog
77+
# Trigger auth if it has not happened yet
78+
auth_ref = self.app.client_manager.auth_ref
79+
if not auth_ref:
80+
raise exceptions.AuthorizationFailure(
81+
"Only an authorized user may issue a new token."
82+
)
8083

8184
data = None
82-
for service in sc.get_data():
85+
for service in auth_ref.service_catalog.catalog:
8386
if (service.get('name') == parsed_args.service or
8487
service.get('type') == parsed_args.service):
8588
data = service
@@ -91,6 +94,6 @@ def take_action(self, parsed_args):
9194
if not data:
9295
self.app.log.error(_('service %s not found\n') %
9396
parsed_args.service)
94-
return ([], [])
97+
return ((), ())
9598

9699
return zip(*sorted(six.iteritems(data)))

openstackclient/identity/v2_0/role.py

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -231,18 +231,19 @@ def take_action(self, parsed_args):
231231
# Project and user are required, if not included in command args
232232
# default to the values used for authentication. For token-flow
233233
# authentication they must be included on the command line.
234+
if (not parsed_args.project and
235+
self.app.client_manager.auth_ref.project_id):
236+
parsed_args.project = auth_ref.project_id
234237
if not parsed_args.project:
235-
if self.app.client_manager.auth_ref:
236-
parsed_args.project = auth_ref.project_id
237-
else:
238-
msg = _("Project must be specified")
239-
raise exceptions.CommandError(msg)
238+
msg = _("Project must be specified")
239+
raise exceptions.CommandError(msg)
240+
241+
if (not parsed_args.user and
242+
self.app.client_manager.auth_ref.user_id):
243+
parsed_args.user = auth_ref.user_id
240244
if not parsed_args.user:
241-
if self.app.client_manager.auth_ref:
242-
parsed_args.user = auth_ref.user_id
243-
else:
244-
msg = _("User must be specified")
245-
raise exceptions.CommandError(msg)
245+
msg = _("User must be specified")
246+
raise exceptions.CommandError(msg)
246247

247248
project = utils.find_resource(
248249
identity_client.tenants,

openstackclient/identity/v2_0/token.py

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import six
1919

2020
from openstackclient.common import command
21+
from openstackclient.common import exceptions
2122
from openstackclient.i18n import _
2223

2324

@@ -32,11 +33,21 @@ def get_parser(self, prog_name):
3233
return parser
3334

3435
def take_action(self, parsed_args):
36+
auth_ref = self.app.client_manager.auth_ref
37+
if not auth_ref:
38+
raise exceptions.AuthorizationFailure(
39+
"Only an authorized user may issue a new token.")
3540

36-
token = self.app.client_manager.auth_ref.service_catalog.get_token()
37-
if 'tenant_id' in token:
38-
token['project_id'] = token.pop('tenant_id')
39-
return zip(*sorted(six.iteritems(token)))
41+
data = {}
42+
if auth_ref.auth_token:
43+
data['id'] = auth_ref.auth_token
44+
if auth_ref.expires:
45+
data['expires'] = auth_ref.expires
46+
if auth_ref.project_id:
47+
data['project_id'] = auth_ref.project_id
48+
if auth_ref.user_id:
49+
data['user_id'] = auth_ref.user_id
50+
return zip(*sorted(six.iteritems(data)))
4051

4152

4253
class RevokeToken(command.Command):

openstackclient/identity/v3/catalog.py

Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
import six
1717

1818
from openstackclient.common import command
19+
from openstackclient.common import exceptions
1920
from openstackclient.common import utils
2021
from openstackclient.i18n import _
2122

@@ -36,13 +37,14 @@ class ListCatalog(command.Lister):
3637

3738
def take_action(self, parsed_args):
3839

39-
# This is ugly because if auth hasn't happened yet we need
40-
# to trigger it here.
41-
sc = self.app.client_manager.session.auth.get_auth_ref(
42-
self.app.client_manager.session,
43-
).service_catalog
40+
# Trigger auth if it has not happened yet
41+
auth_ref = self.app.client_manager.auth_ref
42+
if not auth_ref:
43+
raise exceptions.AuthorizationFailure(
44+
"Only an authorized user may issue a new token."
45+
)
4446

45-
data = sc.get_data()
47+
data = auth_ref.service_catalog.catalog
4648
columns = ('Name', 'Type', 'Endpoints')
4749
return (columns,
4850
(utils.get_dict_properties(
@@ -67,14 +69,15 @@ def get_parser(self, prog_name):
6769

6870
def take_action(self, parsed_args):
6971

70-
# This is ugly because if auth hasn't happened yet we need
71-
# to trigger it here.
72-
sc = self.app.client_manager.session.auth.get_auth_ref(
73-
self.app.client_manager.session,
74-
).service_catalog
72+
# Trigger auth if it has not happened yet
73+
auth_ref = self.app.client_manager.auth_ref
74+
if not auth_ref:
75+
raise exceptions.AuthorizationFailure(
76+
"Only an authorized user may issue a new token."
77+
)
7578

7679
data = None
77-
for service in sc.get_data():
80+
for service in auth_ref.service_catalog.catalog:
7881
if (service.get('name') == parsed_args.service or
7982
service.get('type') == parsed_args.service):
8083
data = dict(service)
@@ -86,6 +89,6 @@ def take_action(self, parsed_args):
8689
if not data:
8790
self.app.log.error(_('service %s not found\n') %
8891
parsed_args.service)
89-
return ([], [])
92+
return ((), ())
9093

9194
return zip(*sorted(six.iteritems(data)))

openstackclient/identity/v3/token.py

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -174,13 +174,23 @@ def get_parser(self, prog_name):
174174
return parser
175175

176176
def take_action(self, parsed_args):
177-
if not self.app.client_manager.auth_ref:
177+
auth_ref = self.app.client_manager.auth_ref
178+
if not auth_ref:
178179
raise exceptions.AuthorizationFailure(
179180
_("Only an authorized user may issue a new token."))
180-
token = self.app.client_manager.auth_ref.service_catalog.get_token()
181-
if 'tenant_id' in token:
182-
token['project_id'] = token.pop('tenant_id')
183-
return zip(*sorted(six.iteritems(token)))
181+
182+
data = {}
183+
if auth_ref.auth_token:
184+
data['id'] = auth_ref.auth_token
185+
if auth_ref.expires:
186+
data['expires'] = auth_ref.expires
187+
if auth_ref.project_id:
188+
data['project_id'] = auth_ref.project_id
189+
if auth_ref.user_id:
190+
data['user_id'] = auth_ref.user_id
191+
if auth_ref.domain_id:
192+
data['domain_id'] = auth_ref.domain_id
193+
return zip(*sorted(six.iteritems(data)))
184194

185195

186196
class RevokeToken(command.Command):

0 commit comments

Comments
 (0)