Skip to content

Commit c3c6edb

Browse files
author
Dean Troyer
committed
Add plugin to support token-endpoint auth
The ksc auth plugins do not have support for the original token-endpoint (aka token flow) auth where the user supplies a token (possibly the Keystone admin_token) and an API endpoint. This is used for bootstrapping Keystone but also has other uses when a scoped user token is provided. The api.auth:TokenEndpoint class is required to provide the same interface methods so all of the special-case code branches to support token-endpoint can be removed. Some additional cleanups related to ClientManager and creating the Compute client also were done to streamline using sessions. Change-Id: I1a6059afa845a591eff92567ca346c09010a93af
1 parent 0c77a9f commit c3c6edb

5 files changed

Lines changed: 124 additions & 58 deletions

File tree

openstackclient/api/auth.py

Lines changed: 61 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818

1919
import stevedore
2020

21+
from oslo.config import cfg
22+
2123
from keystoneclient.auth import base
2224

2325
from openstackclient.common import exceptions as exc
@@ -53,14 +55,14 @@
5355
)
5456

5557

56-
def _guess_authentication_method(options):
58+
def select_auth_plugin(options):
5759
"""If no auth plugin was specified, pick one based on other options"""
5860

59-
if options.os_url:
60-
# service token authentication, do nothing
61-
return
6261
auth_plugin = None
63-
if options.os_password:
62+
if options.os_url and options.os_token:
63+
# service token authentication
64+
auth_plugin = 'token_endpoint'
65+
elif options.os_password:
6466
if options.os_identity_api_version == '3':
6567
auth_plugin = 'v3password'
6668
elif options.os_identity_api_version == '2.0':
@@ -83,14 +85,13 @@ def _guess_authentication_method(options):
8385
)
8486
LOG.debug("No auth plugin selected, picking %s from other "
8587
"options" % auth_plugin)
86-
options.os_auth_plugin = auth_plugin
88+
return auth_plugin
8789

8890

8991
def build_auth_params(cmd_options):
9092
auth_params = {}
91-
if cmd_options.os_url:
92-
return {'token': cmd_options.os_token}
9393
if cmd_options.os_auth_plugin:
94+
LOG.debug('auth_plugin: %s', cmd_options.os_auth_plugin)
9495
auth_plugin = base.get_plugin_class(cmd_options.os_auth_plugin)
9596
plugin_options = auth_plugin.get_options()
9697
for option in plugin_options:
@@ -110,6 +111,7 @@ def build_auth_params(cmd_options):
110111
None,
111112
)
112113
else:
114+
LOG.debug('no auth_plugin')
113115
# delay the plugin choice, grab every option
114116
plugin_options = set([o.replace('-', '_') for o in OPTIONS_LIST])
115117
for option in plugin_options:
@@ -178,3 +180,54 @@ def build_auth_plugins_option_parser(parser):
178180
help=argparse.SUPPRESS,
179181
)
180182
return parser
183+
184+
185+
class TokenEndpoint(base.BaseAuthPlugin):
186+
"""Auth plugin to handle traditional token/endpoint usage
187+
188+
Implements the methods required to handle token authentication
189+
with a user-specified token and service endpoint; no Identity calls
190+
are made for re-scoping, service catalog lookups or the like.
191+
192+
The purpose of this plugin is to get rid of the special-case paths
193+
in the code to handle this authentication format. Its primary use
194+
is for bootstrapping the Keystone database.
195+
"""
196+
197+
def __init__(self, url, token, **kwargs):
198+
"""A plugin for static authentication with an existing token
199+
200+
:param string url: Service endpoint
201+
:param string token: Existing token
202+
"""
203+
super(TokenEndpoint, self).__init__()
204+
self.endpoint = url
205+
self.token = token
206+
207+
def get_endpoint(self, session, **kwargs):
208+
"""Return the supplied endpoint"""
209+
return self.endpoint
210+
211+
def get_token(self, session):
212+
"""Return the supplied token"""
213+
return self.token
214+
215+
def get_auth_ref(self, session, **kwargs):
216+
"""Stub this method for compatibility"""
217+
return None
218+
219+
# Override this because it needs to be a class method...
220+
@classmethod
221+
def get_options(self):
222+
options = super(TokenEndpoint, self).get_options()
223+
224+
options.extend([
225+
# Maintain name 'url' for compatibility
226+
cfg.StrOpt('url',
227+
help='Specific service endpoint to use'),
228+
cfg.StrOpt('token',
229+
secret=True,
230+
help='Authentication token to use'),
231+
])
232+
233+
return options

openstackclient/common/clientmanager.py

Lines changed: 20 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -54,9 +54,10 @@ def __getattr__(self, name):
5454
return self._auth_params[name[1:]]
5555

5656
def __init__(self, auth_options, api_version=None, verify=True):
57-
57+
# If no plugin is named by the user, select one based on
58+
# the supplied options
5859
if not auth_options.os_auth_plugin:
59-
auth._guess_authentication_method(auth_options)
60+
auth_options.os_auth_plugin = auth.select_auth_plugin(auth_options)
6061

6162
self._auth_plugin = auth_options.os_auth_plugin
6263
self._url = auth_options.os_url
@@ -66,7 +67,7 @@ def __init__(self, auth_options, api_version=None, verify=True):
6667
self._service_catalog = None
6768
self.timing = auth_options.timing
6869

69-
# For compatability until all clients can be updated
70+
# For compatibility until all clients can be updated
7071
if 'project_name' in self._auth_params:
7172
self._project_name = self._auth_params['project_name']
7273
elif 'tenant_name' in self._auth_params:
@@ -86,27 +87,25 @@ def __init__(self, auth_options, api_version=None, verify=True):
8687
root_logger = logging.getLogger('')
8788
LOG.setLevel(root_logger.getEffectiveLevel())
8889

89-
self.session = None
90-
if not self._url:
91-
LOG.debug('Using auth plugin: %s' % self._auth_plugin)
92-
auth_plugin = base.get_plugin_class(self._auth_plugin)
93-
self.auth = auth_plugin.load_from_options(**self._auth_params)
94-
# needed by SAML authentication
95-
request_session = requests.session()
96-
self.session = session.Session(
97-
auth=self.auth,
98-
session=request_session,
99-
verify=verify,
100-
)
90+
LOG.debug('Using auth plugin: %s' % self._auth_plugin)
91+
auth_plugin = base.get_plugin_class(self._auth_plugin)
92+
self.auth = auth_plugin.load_from_options(**self._auth_params)
93+
# needed by SAML authentication
94+
request_session = requests.session()
95+
self.session = session.Session(
96+
auth=self.auth,
97+
session=request_session,
98+
verify=verify,
99+
)
101100

102101
self.auth_ref = None
103-
if not self._auth_plugin.endswith("token") and not self._url:
104-
LOG.debug("Populate other password flow attributes")
105-
self.auth_ref = self.session.auth.get_auth_ref(self.session)
106-
self._token = self.session.auth.get_token(self.session)
102+
if 'token' not in self._auth_params:
103+
LOG.debug("Get service catalog")
104+
self.auth_ref = self.auth.get_auth_ref(self.session)
107105
self._service_catalog = self.auth_ref.service_catalog
108-
else:
109-
self._token = self._auth_params.get('token')
106+
107+
# This begone when clients no longer need it...
108+
self._token = self.auth.get_token(self.session)
110109

111110
return
112111

openstackclient/compute/client.py

Lines changed: 4 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -44,33 +44,20 @@ def make_client(instance):
4444

4545
extensions = [extension.Extension('list_extensions', list_extensions)]
4646
client = compute_client(
47-
username=instance._username,
48-
api_key=instance._password,
49-
project_id=instance._project_name,
50-
auth_url=instance._auth_url,
51-
cacert=instance._cacert,
52-
insecure=instance._insecure,
53-
region_name=instance._region_name,
54-
# FIXME(dhellmann): get endpoint_type from option?
55-
endpoint_type='publicURL',
47+
session=instance.session,
5648
extensions=extensions,
57-
service_type=API_NAME,
58-
# FIXME(dhellmann): what is service_name?
59-
service_name='',
6049
http_log_debug=http_log_debug,
6150
timings=instance.timing,
6251
)
6352

6453
# Populate the Nova client to skip another auth query to Identity
65-
if instance._url:
66-
# token flow
67-
client.client.management_url = instance._url
68-
else:
54+
if 'token' not in instance._auth_params:
6955
# password flow
7056
client.client.management_url = instance.get_endpoint_for_service_type(
7157
API_NAME)
7258
client.client.service_catalog = instance._service_catalog
73-
client.client.auth_token = instance._token
59+
client.client.auth_token = instance.auth.get_token(instance.session)
60+
7461
return client
7562

7663

openstackclient/tests/common/test_clientmanager.py

Lines changed: 36 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,31 @@ def setUp(self):
7676
url=fakes.AUTH_URL,
7777
verb='GET')
7878

79+
def test_client_manager_token_endpoint(self):
80+
81+
client_manager = clientmanager.ClientManager(
82+
auth_options=FakeOptions(os_token=fakes.AUTH_TOKEN,
83+
os_url=fakes.AUTH_URL,
84+
os_auth_plugin='token_endpoint'),
85+
api_version=API_VERSION,
86+
verify=True
87+
)
88+
self.assertEqual(
89+
fakes.AUTH_URL,
90+
client_manager._url,
91+
)
92+
93+
self.assertEqual(
94+
fakes.AUTH_TOKEN,
95+
client_manager._token,
96+
)
97+
self.assertIsInstance(
98+
client_manager.auth,
99+
auth.TokenEndpoint,
100+
)
101+
self.assertFalse(client_manager._insecure)
102+
self.assertTrue(client_manager._verify)
103+
79104
def test_client_manager_token(self):
80105

81106
client_manager = clientmanager.ClientManager(
@@ -176,8 +201,7 @@ def test_client_manager_password_verify_ca(self):
176201
self.assertTrue(client_manager._verify)
177202
self.assertEqual('cafile', client_manager._cacert)
178203

179-
def _client_manager_guess_auth_plugin(self, auth_params,
180-
api_version, auth_plugin):
204+
def _select_auth_plugin(self, auth_params, api_version, auth_plugin):
181205
auth_params['os_auth_plugin'] = auth_plugin
182206
auth_params['os_identity_api_version'] = api_version
183207
client_manager = clientmanager.ClientManager(
@@ -190,25 +214,25 @@ def _client_manager_guess_auth_plugin(self, auth_params,
190214
client_manager._auth_plugin,
191215
)
192216

193-
def test_client_manager_guess_auth_plugin(self):
217+
def test_client_manager_select_auth_plugin(self):
194218
# test token auth
195219
params = dict(os_token=fakes.AUTH_TOKEN,
196220
os_auth_url=fakes.AUTH_URL)
197-
self._client_manager_guess_auth_plugin(params, '2.0', 'v2token')
198-
self._client_manager_guess_auth_plugin(params, '3', 'v3token')
199-
self._client_manager_guess_auth_plugin(params, 'XXX', 'token')
200-
# test service auth
221+
self._select_auth_plugin(params, '2.0', 'v2token')
222+
self._select_auth_plugin(params, '3', 'v3token')
223+
self._select_auth_plugin(params, 'XXX', 'token')
224+
# test token/endpoint auth
201225
params = dict(os_token=fakes.AUTH_TOKEN, os_url='test')
202-
self._client_manager_guess_auth_plugin(params, 'XXX', '')
226+
self._select_auth_plugin(params, 'XXX', 'token_endpoint')
203227
# test password auth
204228
params = dict(os_auth_url=fakes.AUTH_URL,
205229
os_username=fakes.USERNAME,
206230
os_password=fakes.PASSWORD)
207-
self._client_manager_guess_auth_plugin(params, '2.0', 'v2password')
208-
self._client_manager_guess_auth_plugin(params, '3', 'v3password')
209-
self._client_manager_guess_auth_plugin(params, 'XXX', 'password')
231+
self._select_auth_plugin(params, '2.0', 'v2password')
232+
self._select_auth_plugin(params, '3', 'v3password')
233+
self._select_auth_plugin(params, 'XXX', 'password')
210234

211-
def test_client_manager_guess_auth_plugin_failure(self):
235+
def test_client_manager_select_auth_plugin_failure(self):
212236
self.assertRaises(exc.CommandError,
213237
clientmanager.ClientManager,
214238
auth_options=FakeOptions(os_auth_plugin=''),

setup.cfg

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,9 @@ packages =
2727
console_scripts =
2828
openstack = openstackclient.shell:main
2929

30+
keystoneclient.auth.plugin =
31+
token_endpoint = openstackclient.api.auth:TokenEndpoint
32+
3033
openstack.cli =
3134
module_list = openstackclient.common.module:ListModule
3235

0 commit comments

Comments
 (0)