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
This commit is contained in:
parent
0c77a9fe8b
commit
c3c6edbe8a
@ -18,6 +18,8 @@ import logging
|
||||
|
||||
import stevedore
|
||||
|
||||
from oslo.config import cfg
|
||||
|
||||
from keystoneclient.auth import base
|
||||
|
||||
from openstackclient.common import exceptions as exc
|
||||
@ -53,14 +55,14 @@ for plugin in PLUGIN_LIST:
|
||||
)
|
||||
|
||||
|
||||
def _guess_authentication_method(options):
|
||||
def select_auth_plugin(options):
|
||||
"""If no auth plugin was specified, pick one based on other options"""
|
||||
|
||||
if options.os_url:
|
||||
# service token authentication, do nothing
|
||||
return
|
||||
auth_plugin = None
|
||||
if options.os_password:
|
||||
if options.os_url and options.os_token:
|
||||
# service token authentication
|
||||
auth_plugin = 'token_endpoint'
|
||||
elif options.os_password:
|
||||
if options.os_identity_api_version == '3':
|
||||
auth_plugin = 'v3password'
|
||||
elif options.os_identity_api_version == '2.0':
|
||||
@ -83,14 +85,13 @@ def _guess_authentication_method(options):
|
||||
)
|
||||
LOG.debug("No auth plugin selected, picking %s from other "
|
||||
"options" % auth_plugin)
|
||||
options.os_auth_plugin = auth_plugin
|
||||
return auth_plugin
|
||||
|
||||
|
||||
def build_auth_params(cmd_options):
|
||||
auth_params = {}
|
||||
if cmd_options.os_url:
|
||||
return {'token': cmd_options.os_token}
|
||||
if cmd_options.os_auth_plugin:
|
||||
LOG.debug('auth_plugin: %s', cmd_options.os_auth_plugin)
|
||||
auth_plugin = base.get_plugin_class(cmd_options.os_auth_plugin)
|
||||
plugin_options = auth_plugin.get_options()
|
||||
for option in plugin_options:
|
||||
@ -110,6 +111,7 @@ def build_auth_params(cmd_options):
|
||||
None,
|
||||
)
|
||||
else:
|
||||
LOG.debug('no auth_plugin')
|
||||
# delay the plugin choice, grab every option
|
||||
plugin_options = set([o.replace('-', '_') for o in OPTIONS_LIST])
|
||||
for option in plugin_options:
|
||||
@ -178,3 +180,54 @@ def build_auth_plugins_option_parser(parser):
|
||||
help=argparse.SUPPRESS,
|
||||
)
|
||||
return parser
|
||||
|
||||
|
||||
class TokenEndpoint(base.BaseAuthPlugin):
|
||||
"""Auth plugin to handle traditional token/endpoint usage
|
||||
|
||||
Implements the methods required to handle token authentication
|
||||
with a user-specified token and service endpoint; no Identity calls
|
||||
are made for re-scoping, service catalog lookups or the like.
|
||||
|
||||
The purpose of this plugin is to get rid of the special-case paths
|
||||
in the code to handle this authentication format. Its primary use
|
||||
is for bootstrapping the Keystone database.
|
||||
"""
|
||||
|
||||
def __init__(self, url, token, **kwargs):
|
||||
"""A plugin for static authentication with an existing token
|
||||
|
||||
:param string url: Service endpoint
|
||||
:param string token: Existing token
|
||||
"""
|
||||
super(TokenEndpoint, self).__init__()
|
||||
self.endpoint = url
|
||||
self.token = token
|
||||
|
||||
def get_endpoint(self, session, **kwargs):
|
||||
"""Return the supplied endpoint"""
|
||||
return self.endpoint
|
||||
|
||||
def get_token(self, session):
|
||||
"""Return the supplied token"""
|
||||
return self.token
|
||||
|
||||
def get_auth_ref(self, session, **kwargs):
|
||||
"""Stub this method for compatibility"""
|
||||
return None
|
||||
|
||||
# Override this because it needs to be a class method...
|
||||
@classmethod
|
||||
def get_options(self):
|
||||
options = super(TokenEndpoint, self).get_options()
|
||||
|
||||
options.extend([
|
||||
# Maintain name 'url' for compatibility
|
||||
cfg.StrOpt('url',
|
||||
help='Specific service endpoint to use'),
|
||||
cfg.StrOpt('token',
|
||||
secret=True,
|
||||
help='Authentication token to use'),
|
||||
])
|
||||
|
||||
return options
|
||||
|
@ -54,9 +54,10 @@ class ClientManager(object):
|
||||
return self._auth_params[name[1:]]
|
||||
|
||||
def __init__(self, auth_options, api_version=None, verify=True):
|
||||
|
||||
# If no plugin is named by the user, select one based on
|
||||
# the supplied options
|
||||
if not auth_options.os_auth_plugin:
|
||||
auth._guess_authentication_method(auth_options)
|
||||
auth_options.os_auth_plugin = auth.select_auth_plugin(auth_options)
|
||||
|
||||
self._auth_plugin = auth_options.os_auth_plugin
|
||||
self._url = auth_options.os_url
|
||||
@ -66,7 +67,7 @@ class ClientManager(object):
|
||||
self._service_catalog = None
|
||||
self.timing = auth_options.timing
|
||||
|
||||
# For compatability until all clients can be updated
|
||||
# For compatibility until all clients can be updated
|
||||
if 'project_name' in self._auth_params:
|
||||
self._project_name = self._auth_params['project_name']
|
||||
elif 'tenant_name' in self._auth_params:
|
||||
@ -86,8 +87,6 @@ class ClientManager(object):
|
||||
root_logger = logging.getLogger('')
|
||||
LOG.setLevel(root_logger.getEffectiveLevel())
|
||||
|
||||
self.session = None
|
||||
if not self._url:
|
||||
LOG.debug('Using auth plugin: %s' % self._auth_plugin)
|
||||
auth_plugin = base.get_plugin_class(self._auth_plugin)
|
||||
self.auth = auth_plugin.load_from_options(**self._auth_params)
|
||||
@ -100,13 +99,13 @@ class ClientManager(object):
|
||||
)
|
||||
|
||||
self.auth_ref = None
|
||||
if not self._auth_plugin.endswith("token") and not self._url:
|
||||
LOG.debug("Populate other password flow attributes")
|
||||
self.auth_ref = self.session.auth.get_auth_ref(self.session)
|
||||
self._token = self.session.auth.get_token(self.session)
|
||||
if 'token' not in self._auth_params:
|
||||
LOG.debug("Get service catalog")
|
||||
self.auth_ref = self.auth.get_auth_ref(self.session)
|
||||
self._service_catalog = self.auth_ref.service_catalog
|
||||
else:
|
||||
self._token = self._auth_params.get('token')
|
||||
|
||||
# This begone when clients no longer need it...
|
||||
self._token = self.auth.get_token(self.session)
|
||||
|
||||
return
|
||||
|
||||
|
@ -44,33 +44,20 @@ def make_client(instance):
|
||||
|
||||
extensions = [extension.Extension('list_extensions', list_extensions)]
|
||||
client = compute_client(
|
||||
username=instance._username,
|
||||
api_key=instance._password,
|
||||
project_id=instance._project_name,
|
||||
auth_url=instance._auth_url,
|
||||
cacert=instance._cacert,
|
||||
insecure=instance._insecure,
|
||||
region_name=instance._region_name,
|
||||
# FIXME(dhellmann): get endpoint_type from option?
|
||||
endpoint_type='publicURL',
|
||||
session=instance.session,
|
||||
extensions=extensions,
|
||||
service_type=API_NAME,
|
||||
# FIXME(dhellmann): what is service_name?
|
||||
service_name='',
|
||||
http_log_debug=http_log_debug,
|
||||
timings=instance.timing,
|
||||
)
|
||||
|
||||
# Populate the Nova client to skip another auth query to Identity
|
||||
if instance._url:
|
||||
# token flow
|
||||
client.client.management_url = instance._url
|
||||
else:
|
||||
if 'token' not in instance._auth_params:
|
||||
# password flow
|
||||
client.client.management_url = instance.get_endpoint_for_service_type(
|
||||
API_NAME)
|
||||
client.client.service_catalog = instance._service_catalog
|
||||
client.client.auth_token = instance._token
|
||||
client.client.auth_token = instance.auth.get_token(instance.session)
|
||||
|
||||
return client
|
||||
|
||||
|
||||
|
@ -76,6 +76,31 @@ class TestClientManager(utils.TestCase):
|
||||
url=fakes.AUTH_URL,
|
||||
verb='GET')
|
||||
|
||||
def test_client_manager_token_endpoint(self):
|
||||
|
||||
client_manager = clientmanager.ClientManager(
|
||||
auth_options=FakeOptions(os_token=fakes.AUTH_TOKEN,
|
||||
os_url=fakes.AUTH_URL,
|
||||
os_auth_plugin='token_endpoint'),
|
||||
api_version=API_VERSION,
|
||||
verify=True
|
||||
)
|
||||
self.assertEqual(
|
||||
fakes.AUTH_URL,
|
||||
client_manager._url,
|
||||
)
|
||||
|
||||
self.assertEqual(
|
||||
fakes.AUTH_TOKEN,
|
||||
client_manager._token,
|
||||
)
|
||||
self.assertIsInstance(
|
||||
client_manager.auth,
|
||||
auth.TokenEndpoint,
|
||||
)
|
||||
self.assertFalse(client_manager._insecure)
|
||||
self.assertTrue(client_manager._verify)
|
||||
|
||||
def test_client_manager_token(self):
|
||||
|
||||
client_manager = clientmanager.ClientManager(
|
||||
@ -176,8 +201,7 @@ class TestClientManager(utils.TestCase):
|
||||
self.assertTrue(client_manager._verify)
|
||||
self.assertEqual('cafile', client_manager._cacert)
|
||||
|
||||
def _client_manager_guess_auth_plugin(self, auth_params,
|
||||
api_version, auth_plugin):
|
||||
def _select_auth_plugin(self, auth_params, api_version, auth_plugin):
|
||||
auth_params['os_auth_plugin'] = auth_plugin
|
||||
auth_params['os_identity_api_version'] = api_version
|
||||
client_manager = clientmanager.ClientManager(
|
||||
@ -190,25 +214,25 @@ class TestClientManager(utils.TestCase):
|
||||
client_manager._auth_plugin,
|
||||
)
|
||||
|
||||
def test_client_manager_guess_auth_plugin(self):
|
||||
def test_client_manager_select_auth_plugin(self):
|
||||
# test token auth
|
||||
params = dict(os_token=fakes.AUTH_TOKEN,
|
||||
os_auth_url=fakes.AUTH_URL)
|
||||
self._client_manager_guess_auth_plugin(params, '2.0', 'v2token')
|
||||
self._client_manager_guess_auth_plugin(params, '3', 'v3token')
|
||||
self._client_manager_guess_auth_plugin(params, 'XXX', 'token')
|
||||
# test service auth
|
||||
self._select_auth_plugin(params, '2.0', 'v2token')
|
||||
self._select_auth_plugin(params, '3', 'v3token')
|
||||
self._select_auth_plugin(params, 'XXX', 'token')
|
||||
# test token/endpoint auth
|
||||
params = dict(os_token=fakes.AUTH_TOKEN, os_url='test')
|
||||
self._client_manager_guess_auth_plugin(params, 'XXX', '')
|
||||
self._select_auth_plugin(params, 'XXX', 'token_endpoint')
|
||||
# test password auth
|
||||
params = dict(os_auth_url=fakes.AUTH_URL,
|
||||
os_username=fakes.USERNAME,
|
||||
os_password=fakes.PASSWORD)
|
||||
self._client_manager_guess_auth_plugin(params, '2.0', 'v2password')
|
||||
self._client_manager_guess_auth_plugin(params, '3', 'v3password')
|
||||
self._client_manager_guess_auth_plugin(params, 'XXX', 'password')
|
||||
self._select_auth_plugin(params, '2.0', 'v2password')
|
||||
self._select_auth_plugin(params, '3', 'v3password')
|
||||
self._select_auth_plugin(params, 'XXX', 'password')
|
||||
|
||||
def test_client_manager_guess_auth_plugin_failure(self):
|
||||
def test_client_manager_select_auth_plugin_failure(self):
|
||||
self.assertRaises(exc.CommandError,
|
||||
clientmanager.ClientManager,
|
||||
auth_options=FakeOptions(os_auth_plugin=''),
|
||||
|
Loading…
Reference in New Issue
Block a user