Merge "Add plugin to support token-endpoint auth"

This commit is contained in:
Jenkins 2014-10-17 23:27:28 +00:00 committed by Gerrit Code Review
commit 68130fa921
5 changed files with 124 additions and 58 deletions

View File

@ -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

View File

@ -56,9 +56,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
@ -68,7 +69,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:
@ -88,27 +89,25 @@ 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)
# needed by SAML authentication
request_session = requests.session()
self.session = session.Session(
auth=self.auth,
session=request_session,
verify=verify,
)
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)
# needed by SAML authentication
request_session = requests.session()
self.session = session.Session(
auth=self.auth,
session=request_session,
verify=verify,
)
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

View File

@ -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, region_name=instance._region_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

View File

@ -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=''),

View File

@ -27,6 +27,9 @@ packages =
console_scripts =
openstack = openstackclient.shell:main
keystoneclient.auth.plugin =
token_endpoint = openstackclient.api.auth:TokenEndpoint
openstack.cli =
command_list = openstackclient.common.module:ListCommand
module_list = openstackclient.common.module:ListModule