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
This commit is contained in:
Navid Pustchi 2016-02-04 16:45:38 +00:00 committed by Alvaro Lopez Garcia
parent ada6abb30e
commit 6ae0d2e8a5
18 changed files with 330 additions and 146 deletions

View File

@ -16,15 +16,12 @@
import argparse import argparse
import logging import logging
import stevedore from keystoneauth1.loading import base
from keystoneclient.auth import base
from openstackclient.common import exceptions as exc from openstackclient.common import exceptions as exc
from openstackclient.common import utils from openstackclient.common import utils
from openstackclient.i18n import _ from openstackclient.i18n import _
LOG = logging.getLogger(__name__) LOG = logging.getLogger(__name__)
# Initialize the list of Authentication plugins early in order # Initialize the list of Authentication plugins early in order
@ -37,15 +34,10 @@ OPTIONS_LIST = {}
def get_plugin_list(): def get_plugin_list():
"""Gather plugin list and cache it""" """Gather plugin list and cache it"""
global PLUGIN_LIST global PLUGIN_LIST
if PLUGIN_LIST is None: if PLUGIN_LIST is None:
PLUGIN_LIST = stevedore.ExtensionManager( PLUGIN_LIST = base.get_available_plugin_names()
base.PLUGIN_NAMESPACE,
invoke_on_load=False,
propagate_map_exceptions=True,
)
return PLUGIN_LIST return PLUGIN_LIST
@ -55,8 +47,9 @@ def get_options_list():
global OPTIONS_LIST global OPTIONS_LIST
if not OPTIONS_LIST: if not OPTIONS_LIST:
for plugin in get_plugin_list(): for plugin_name in get_plugin_list():
for o in plugin.plugin.get_options(): plugin_options = base.get_plugin_options(plugin_name)
for o in plugin_options:
os_name = o.dest.lower().replace('_', '-') os_name = o.dest.lower().replace('_', '-')
os_env_name = 'OS_' + os_name.upper().replace('-', '_') os_env_name = 'OS_' + os_name.upper().replace('-', '_')
OPTIONS_LIST.setdefault( OPTIONS_LIST.setdefault(
@ -66,7 +59,7 @@ def get_options_list():
# help texts if they vary from one auth plugin to another # help texts if they vary from one auth plugin to another
# also the text rendering is ugly in the CLI ... # also the text rendering is ugly in the CLI ...
OPTIONS_LIST[os_name]['help'] += 'With %s: %s\n' % ( OPTIONS_LIST[os_name]['help'] += 'With %s: %s\n' % (
plugin.name, plugin_name,
o.help, o.help,
) )
return OPTIONS_LIST return OPTIONS_LIST
@ -83,7 +76,7 @@ def select_auth_plugin(options):
if options.auth.get('url') and options.auth.get('token'): if options.auth.get('url') and options.auth.get('token'):
# service token authentication # service token authentication
auth_plugin_name = 'token_endpoint' auth_plugin_name = 'token_endpoint'
elif options.auth_type in [plugin.name for plugin in PLUGIN_LIST]: elif options.auth_type in PLUGIN_LIST:
# A direct plugin name was given, use it # A direct plugin name was given, use it
auth_plugin_name = options.auth_type auth_plugin_name = options.auth_type
elif options.auth.get('username'): elif options.auth.get('username'):
@ -115,7 +108,7 @@ def build_auth_params(auth_plugin_name, cmd_options):
auth_params = dict(cmd_options.auth) auth_params = dict(cmd_options.auth)
if auth_plugin_name: if auth_plugin_name:
LOG.debug('auth_type: %s', auth_plugin_name) LOG.debug('auth_type: %s', auth_plugin_name)
auth_plugin_class = base.get_plugin_class(auth_plugin_name) auth_plugin_loader = base.get_plugin_loader(auth_plugin_name)
# grab tenant from project for v2.0 API compatibility # grab tenant from project for v2.0 API compatibility
if auth_plugin_name.startswith("v2"): if auth_plugin_name.startswith("v2"):
if 'project_id' in auth_params: if 'project_id' in auth_params:
@ -127,12 +120,12 @@ def build_auth_params(auth_plugin_name, cmd_options):
else: else:
LOG.debug('no auth_type') LOG.debug('no auth_type')
# delay the plugin choice, grab every option # delay the plugin choice, grab every option
auth_plugin_class = None auth_plugin_loader = None
plugin_options = set([o.replace('-', '_') for o in get_options_list()]) plugin_options = set([o.replace('-', '_') for o in get_options_list()])
for option in plugin_options: for option in plugin_options:
LOG.debug('fetching option %s', option) LOG.debug('fetching option %s', option)
auth_params[option] = getattr(cmd_options.auth, option, None) auth_params[option] = getattr(cmd_options.auth, option, None)
return (auth_plugin_class, auth_params) return (auth_plugin_loader, auth_params)
def check_valid_auth_options(options, auth_plugin_name, required_scope=True): def check_valid_auth_options(options, auth_plugin_name, required_scope=True):
@ -188,7 +181,7 @@ def build_auth_plugins_option_parser(parser):
authentication plugin. authentication plugin.
""" """
available_plugins = [plugin.name for plugin in get_plugin_list()] available_plugins = list(get_plugin_list())
parser.add_argument( parser.add_argument(
'--os-auth-type', '--os-auth-type',
metavar='<auth-type>', metavar='<auth-type>',

View File

@ -18,13 +18,13 @@ import logging
from oslo_config import cfg from oslo_config import cfg
from six.moves.urllib import parse as urlparse from six.moves.urllib import parse as urlparse
from keystoneclient.auth.identity.generic import password as ksc_password from keystoneauth1.loading._plugins import admin_token as token_endpoint
from keystoneclient.auth import token_endpoint from keystoneauth1.loading._plugins.identity import generic as ksa_password
LOG = logging.getLogger(__name__) LOG = logging.getLogger(__name__)
class TokenEndpoint(token_endpoint.Token): class TokenEndpoint(token_endpoint.AdminToken):
"""Auth plugin to handle traditional token/endpoint usage """Auth plugin to handle traditional token/endpoint usage
Implements the methods required to handle token authentication Implements the methods required to handle token authentication
@ -36,20 +36,15 @@ class TokenEndpoint(token_endpoint.Token):
is for bootstrapping the Keystone database. is for bootstrapping the Keystone database.
""" """
def __init__(self, url, token, **kwargs): def load_from_options(self, url, token):
"""A plugin for static authentication with an existing token """A plugin for static authentication with an existing token
:param string url: Service endpoint :param string url: Service endpoint
:param string token: Existing token :param string token: Existing token
""" """
super(TokenEndpoint, self).__init__(endpoint=url, return super(TokenEndpoint, self).load_from_options(endpoint=url,
token=token) token=token)
def get_auth_ref(self, session, **kwargs):
# Stub this method for compatibility
return None
@classmethod
def get_options(self): def get_options(self):
options = super(TokenEndpoint, self).get_options() options = super(TokenEndpoint, self).get_options()
@ -65,7 +60,7 @@ class TokenEndpoint(token_endpoint.Token):
return options return options
class OSCGenericPassword(ksc_password.Password): class OSCGenericPassword(ksa_password.Password):
"""Auth plugin hack to work around broken Keystone configurations """Auth plugin hack to work around broken Keystone configurations
The default Keystone configuration uses http://localhost:xxxx in The default Keystone configuration uses http://localhost:xxxx in

View File

@ -269,7 +269,7 @@ class ClientManager(object):
endpoint = self.auth_ref.service_catalog.url_for( endpoint = self.auth_ref.service_catalog.url_for(
service_type=service_type, service_type=service_type,
region_name=region_name, region_name=region_name,
endpoint_type=interface, interface=interface,
) )
else: else:
# Get the passed endpoint directly from the auth plugin # Get the passed endpoint directly from the auth plugin

View File

@ -16,6 +16,7 @@
import six import six
from openstackclient.common import command from openstackclient.common import command
from openstackclient.common import exceptions
from openstackclient.common import utils from openstackclient.common import utils
from openstackclient.i18n import _ from openstackclient.i18n import _
@ -41,13 +42,14 @@ class ListCatalog(command.Lister):
def take_action(self, parsed_args): def take_action(self, parsed_args):
# This is ugly because if auth hasn't happened yet we need # Trigger auth if it has not happened yet
# to trigger it here. auth_ref = self.app.client_manager.auth_ref
sc = self.app.client_manager.session.auth.get_auth_ref( if not auth_ref:
self.app.client_manager.session, raise exceptions.AuthorizationFailure(
).service_catalog "Only an authorized user may issue a new token."
)
data = sc.get_data() data = auth_ref.service_catalog.catalog
columns = ('Name', 'Type', 'Endpoints') columns = ('Name', 'Type', 'Endpoints')
return (columns, return (columns,
(utils.get_dict_properties( (utils.get_dict_properties(
@ -72,14 +74,15 @@ class ShowCatalog(command.ShowOne):
def take_action(self, parsed_args): def take_action(self, parsed_args):
# This is ugly because if auth hasn't happened yet we need # Trigger auth if it has not happened yet
# to trigger it here. auth_ref = self.app.client_manager.auth_ref
sc = self.app.client_manager.session.auth.get_auth_ref( if not auth_ref:
self.app.client_manager.session, raise exceptions.AuthorizationFailure(
).service_catalog "Only an authorized user may issue a new token."
)
data = None data = None
for service in sc.get_data(): for service in auth_ref.service_catalog.catalog:
if (service.get('name') == parsed_args.service or if (service.get('name') == parsed_args.service or
service.get('type') == parsed_args.service): service.get('type') == parsed_args.service):
data = service data = service
@ -91,6 +94,6 @@ class ShowCatalog(command.ShowOne):
if not data: if not data:
self.app.log.error(_('service %s not found\n') % self.app.log.error(_('service %s not found\n') %
parsed_args.service) parsed_args.service)
return ([], []) return ((), ())
return zip(*sorted(six.iteritems(data))) return zip(*sorted(six.iteritems(data)))

View File

@ -231,16 +231,17 @@ class ListUserRole(command.Lister):
# Project and user are required, if not included in command args # Project and user are required, if not included in command args
# default to the values used for authentication. For token-flow # default to the values used for authentication. For token-flow
# authentication they must be included on the command line. # authentication they must be included on the command line.
if not parsed_args.project: if (not parsed_args.project and
if self.app.client_manager.auth_ref: self.app.client_manager.auth_ref.project_id):
parsed_args.project = auth_ref.project_id parsed_args.project = auth_ref.project_id
else: if not parsed_args.project:
msg = _("Project must be specified") msg = _("Project must be specified")
raise exceptions.CommandError(msg) raise exceptions.CommandError(msg)
if not parsed_args.user:
if self.app.client_manager.auth_ref: if (not parsed_args.user and
self.app.client_manager.auth_ref.user_id):
parsed_args.user = auth_ref.user_id parsed_args.user = auth_ref.user_id
else: if not parsed_args.user:
msg = _("User must be specified") msg = _("User must be specified")
raise exceptions.CommandError(msg) raise exceptions.CommandError(msg)

View File

@ -18,6 +18,7 @@
import six import six
from openstackclient.common import command from openstackclient.common import command
from openstackclient.common import exceptions
from openstackclient.i18n import _ from openstackclient.i18n import _
@ -32,11 +33,21 @@ class IssueToken(command.ShowOne):
return parser return parser
def take_action(self, parsed_args): def take_action(self, parsed_args):
auth_ref = self.app.client_manager.auth_ref
if not auth_ref:
raise exceptions.AuthorizationFailure(
"Only an authorized user may issue a new token.")
token = self.app.client_manager.auth_ref.service_catalog.get_token() data = {}
if 'tenant_id' in token: if auth_ref.auth_token:
token['project_id'] = token.pop('tenant_id') data['id'] = auth_ref.auth_token
return zip(*sorted(six.iteritems(token))) if auth_ref.expires:
data['expires'] = auth_ref.expires
if auth_ref.project_id:
data['project_id'] = auth_ref.project_id
if auth_ref.user_id:
data['user_id'] = auth_ref.user_id
return zip(*sorted(six.iteritems(data)))
class RevokeToken(command.Command): class RevokeToken(command.Command):

View File

@ -16,6 +16,7 @@
import six import six
from openstackclient.common import command from openstackclient.common import command
from openstackclient.common import exceptions
from openstackclient.common import utils from openstackclient.common import utils
from openstackclient.i18n import _ from openstackclient.i18n import _
@ -36,13 +37,14 @@ class ListCatalog(command.Lister):
def take_action(self, parsed_args): def take_action(self, parsed_args):
# This is ugly because if auth hasn't happened yet we need # Trigger auth if it has not happened yet
# to trigger it here. auth_ref = self.app.client_manager.auth_ref
sc = self.app.client_manager.session.auth.get_auth_ref( if not auth_ref:
self.app.client_manager.session, raise exceptions.AuthorizationFailure(
).service_catalog "Only an authorized user may issue a new token."
)
data = sc.get_data() data = auth_ref.service_catalog.catalog
columns = ('Name', 'Type', 'Endpoints') columns = ('Name', 'Type', 'Endpoints')
return (columns, return (columns,
(utils.get_dict_properties( (utils.get_dict_properties(
@ -67,14 +69,15 @@ class ShowCatalog(command.ShowOne):
def take_action(self, parsed_args): def take_action(self, parsed_args):
# This is ugly because if auth hasn't happened yet we need # Trigger auth if it has not happened yet
# to trigger it here. auth_ref = self.app.client_manager.auth_ref
sc = self.app.client_manager.session.auth.get_auth_ref( if not auth_ref:
self.app.client_manager.session, raise exceptions.AuthorizationFailure(
).service_catalog "Only an authorized user may issue a new token."
)
data = None data = None
for service in sc.get_data(): for service in auth_ref.service_catalog.catalog:
if (service.get('name') == parsed_args.service or if (service.get('name') == parsed_args.service or
service.get('type') == parsed_args.service): service.get('type') == parsed_args.service):
data = dict(service) data = dict(service)
@ -86,6 +89,6 @@ class ShowCatalog(command.ShowOne):
if not data: if not data:
self.app.log.error(_('service %s not found\n') % self.app.log.error(_('service %s not found\n') %
parsed_args.service) parsed_args.service)
return ([], []) return ((), ())
return zip(*sorted(six.iteritems(data))) return zip(*sorted(six.iteritems(data)))

View File

@ -174,13 +174,23 @@ class IssueToken(command.ShowOne):
return parser return parser
def take_action(self, parsed_args): def take_action(self, parsed_args):
if not self.app.client_manager.auth_ref: auth_ref = self.app.client_manager.auth_ref
if not auth_ref:
raise exceptions.AuthorizationFailure( raise exceptions.AuthorizationFailure(
_("Only an authorized user may issue a new token.")) _("Only an authorized user may issue a new token."))
token = self.app.client_manager.auth_ref.service_catalog.get_token()
if 'tenant_id' in token: data = {}
token['project_id'] = token.pop('tenant_id') if auth_ref.auth_token:
return zip(*sorted(six.iteritems(token))) data['id'] = auth_ref.auth_token
if auth_ref.expires:
data['expires'] = auth_ref.expires
if auth_ref.project_id:
data['project_id'] = auth_ref.project_id
if auth_ref.user_id:
data['user_id'] = auth_ref.user_id
if auth_ref.domain_id:
data['domain_id'] = auth_ref.domain_id
return zip(*sorted(six.iteritems(data)))
class RevokeToken(command.Command): class RevokeToken(command.Command):

View File

@ -17,11 +17,11 @@ import json as jsonutils
import mock import mock
from requests_mock.contrib import fixture from requests_mock.contrib import fixture
from keystoneclient.auth.identity import v2 as auth_v2 from keystoneauth1.access import service_catalog
from keystoneclient import service_catalog from keystoneauth1.identity import v2 as auth_v2
from keystoneauth1 import token_endpoint
from openstackclient.api import auth from openstackclient.api import auth
from openstackclient.api import auth_plugin
from openstackclient.common import clientmanager from openstackclient.common import clientmanager
from openstackclient.common import exceptions as exc from openstackclient.common import exceptions as exc
from openstackclient.tests import fakes from openstackclient.tests import fakes
@ -29,7 +29,6 @@ from openstackclient.tests import utils
API_VERSION = {"identity": "2.0"} API_VERSION = {"identity": "2.0"}
AUTH_REF = {'version': 'v2.0'} AUTH_REF = {'version': 'v2.0'}
AUTH_REF.update(fakes.TEST_RESPONSE_DICT['access']) AUTH_REF.update(fakes.TEST_RESPONSE_DICT['access'])
SERVICE_CATALOG = service_catalog.ServiceCatalogV2(AUTH_REF) SERVICE_CATALOG = service_catalog.ServiceCatalogV2(AUTH_REF)
@ -126,7 +125,7 @@ class TestClientManager(utils.TestCase):
) )
self.assertIsInstance( self.assertIsInstance(
client_manager.auth, client_manager.auth,
auth_plugin.TokenEndpoint, token_endpoint.Token,
) )
self.assertFalse(client_manager._insecure) self.assertFalse(client_manager._insecure)
self.assertTrue(client_manager._verify) self.assertTrue(client_manager._verify)
@ -205,11 +204,14 @@ class TestClientManager(utils.TestCase):
) )
self.assertTrue(client_manager._insecure) self.assertTrue(client_manager._insecure)
self.assertFalse(client_manager._verify) self.assertFalse(client_manager._verify)
# These need to stick around until the old-style clients are gone # These need to stick around until the old-style clients are gone
self.assertEqual( self.assertEqual(
AUTH_REF, AUTH_REF.pop('version'),
client_manager.auth_ref, client_manager.auth_ref.version,
)
self.assertEqual(
fakes.to_unicode_dict(AUTH_REF),
client_manager.auth_ref._data['access'],
) )
self.assertEqual( self.assertEqual(
dir(SERVICE_CATALOG), dir(SERVICE_CATALOG),
@ -296,9 +298,10 @@ class TestClientManager(utils.TestCase):
def _select_auth_plugin(self, auth_params, api_version, auth_plugin_name): def _select_auth_plugin(self, auth_params, api_version, auth_plugin_name):
auth_params['auth_type'] = auth_plugin_name auth_params['auth_type'] = auth_plugin_name
auth_params['identity_api_version'] = api_version auth_params['identity_api_version'] = api_version
client_manager = clientmanager.ClientManager( client_manager = clientmanager.ClientManager(
cli_options=FakeOptions(**auth_params), cli_options=FakeOptions(**auth_params),
api_version=API_VERSION, api_version={"identity": api_version},
verify=True verify=True
) )
client_manager.setup_auth() client_manager.setup_auth()

View File

@ -50,6 +50,21 @@ TEST_RESPONSE_DICT_V3.set_project_scope()
TEST_VERSIONS = fixture.DiscoveryList(href=AUTH_URL) TEST_VERSIONS = fixture.DiscoveryList(href=AUTH_URL)
def to_unicode_dict(catalog_dict):
"""Converts dict to unicode dict
"""
if isinstance(catalog_dict, dict):
return {to_unicode_dict(key): to_unicode_dict(value)
for key, value in catalog_dict.items()}
elif isinstance(catalog_dict, list):
return [to_unicode_dict(element) for element in catalog_dict]
elif isinstance(catalog_dict, str):
return catalog_dict + u""
else:
return catalog_dict
class FakeStdout(object): class FakeStdout(object):
def __init__(self): def __init__(self):

View File

@ -17,6 +17,9 @@ import copy
import mock import mock
import uuid import uuid
from keystoneauth1 import access
from keystoneauth1 import fixture
from openstackclient.tests import fakes from openstackclient.tests import fakes
from openstackclient.tests import utils from openstackclient.tests import utils
@ -109,6 +112,43 @@ ENDPOINT = {
} }
def fake_auth_ref(fake_token, fake_service=None):
"""Create an auth_ref using keystoneauth's fixtures"""
token_copy = copy.deepcopy(fake_token)
token_copy['token_id'] = token_copy.pop('id')
token = fixture.V2Token(**token_copy)
# An auth_ref is actually an access info object
auth_ref = access.create(body=token)
# Create a service catalog
if fake_service:
service = token.add_service(
fake_service['type'],
fake_service['name'],
)
# TODO(dtroyer): Add an 'id' element to KSA's _Service fixure
service['id'] = fake_service['id']
for e in fake_service['endpoints']:
# KSA's _Service fixture copies publicURL to internalURL and
# adminURL if they do not exist. Soooo helpful...
internal = e.get('internalURL', None)
admin = e.get('adminURL', None)
region = e.get('region_id') or e.get('region', '<none>')
endpoint = service.add_endpoint(
public=e['publicURL'],
internal=internal,
admin=admin,
region=region,
)
# ...so undo that helpfulness
if not internal:
endpoint['internalURL'] = None
if not admin:
endpoint['adminURL'] = None
return auth_ref
class FakeIdentityv2Client(object): class FakeIdentityv2Client(object):
def __init__(self, **kwargs): def __init__(self, **kwargs):

View File

@ -14,6 +14,7 @@
import mock import mock
from openstackclient.identity.v2_0 import catalog from openstackclient.identity.v2_0 import catalog
from openstackclient.tests.identity.v2_0 import fakes as identity_fakes
from openstackclient.tests import utils from openstackclient.tests import utils
@ -49,7 +50,7 @@ class TestCatalog(utils.TestCommand):
super(TestCatalog, self).setUp() super(TestCatalog, self).setUp()
self.sc_mock = mock.MagicMock() self.sc_mock = mock.MagicMock()
self.sc_mock.service_catalog.get_data.return_value = [ self.sc_mock.service_catalog.catalog.return_value = [
self.fake_service, self.fake_service,
] ]
@ -74,6 +75,13 @@ class TestCatalogList(TestCatalog):
self.cmd = catalog.ListCatalog(self.app, None) self.cmd = catalog.ListCatalog(self.app, None)
def test_catalog_list(self): def test_catalog_list(self):
auth_ref = identity_fakes.fake_auth_ref(
identity_fakes.TOKEN,
fake_service=self.fake_service,
)
self.ar_mock = mock.PropertyMock(return_value=auth_ref)
type(self.app.client_manager).auth_ref = self.ar_mock
arglist = [] arglist = []
verifylist = [] verifylist = []
parsed_args = self.check_parser(self.cmd, arglist, verifylist) parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@ -82,7 +90,6 @@ class TestCatalogList(TestCatalog):
# returns a tuple containing the column names and an iterable # returns a tuple containing the column names and an iterable
# containing the data to be listed. # containing the data to be listed.
columns, data = self.cmd.take_action(parsed_args) columns, data = self.cmd.take_action(parsed_args)
self.sc_mock.service_catalog.get_data.assert_called_with()
self.assertEqual(self.columns, columns) self.assertEqual(self.columns, columns)
datalist = (( datalist = ((
@ -117,9 +124,12 @@ class TestCatalogList(TestCatalog):
}, },
], ],
} }
self.sc_mock.service_catalog.get_data.return_value = [ auth_ref = identity_fakes.fake_auth_ref(
fake_service, identity_fakes.TOKEN,
] fake_service=fake_service,
)
self.ar_mock = mock.PropertyMock(return_value=auth_ref)
type(self.app.client_manager).auth_ref = self.ar_mock
arglist = [] arglist = []
verifylist = [] verifylist = []
@ -129,7 +139,6 @@ class TestCatalogList(TestCatalog):
# returns a tuple containing the column names and an iterable # returns a tuple containing the column names and an iterable
# containing the data to be listed. # containing the data to be listed.
columns, data = self.cmd.take_action(parsed_args) columns, data = self.cmd.take_action(parsed_args)
self.sc_mock.service_catalog.get_data.assert_called_with()
self.assertEqual(self.columns, columns) self.assertEqual(self.columns, columns)
datalist = (( datalist = ((
@ -151,6 +160,13 @@ class TestCatalogShow(TestCatalog):
self.cmd = catalog.ShowCatalog(self.app, None) self.cmd = catalog.ShowCatalog(self.app, None)
def test_catalog_show(self): def test_catalog_show(self):
auth_ref = identity_fakes.fake_auth_ref(
identity_fakes.UNSCOPED_TOKEN,
fake_service=self.fake_service,
)
self.ar_mock = mock.PropertyMock(return_value=auth_ref)
type(self.app.client_manager).auth_ref = self.ar_mock
arglist = [ arglist = [
'compute', 'compute',
] ]
@ -163,7 +179,6 @@ class TestCatalogShow(TestCatalog):
# returns a two-part tuple with a tuple of column names and a tuple of # returns a two-part tuple with a tuple of column names and a tuple of
# data to be shown. # data to be shown.
columns, data = self.cmd.take_action(parsed_args) columns, data = self.cmd.take_action(parsed_args)
self.sc_mock.service_catalog.get_data.assert_called_with()
collist = ('endpoints', 'id', 'name', 'type') collist = ('endpoints', 'id', 'name', 'type')
self.assertEqual(collist, columns) self.assertEqual(collist, columns)

View File

@ -26,6 +26,13 @@ from openstackclient.tests.identity.v2_0 import fakes as identity_fakes
class TestRole(identity_fakes.TestIdentityv2): class TestRole(identity_fakes.TestIdentityv2):
fake_service = copy.deepcopy(identity_fakes.SERVICE)
fake_service['endpoints'] = [
{
'publicURL': identity_fakes.ENDPOINT['publicurl'],
},
]
def setUp(self): def setUp(self):
super(TestRole, self).setUp() super(TestRole, self).setUp()
@ -41,6 +48,13 @@ class TestRole(identity_fakes.TestIdentityv2):
self.roles_mock = self.app.client_manager.identity.roles self.roles_mock = self.app.client_manager.identity.roles
self.roles_mock.reset_mock() self.roles_mock.reset_mock()
auth_ref = identity_fakes.fake_auth_ref(
identity_fakes.TOKEN,
fake_service=self.fake_service,
)
self.ar_mock = mock.PropertyMock(return_value=auth_ref)
type(self.app.client_manager).auth_ref = self.ar_mock
class TestRoleAdd(TestRole): class TestRoleAdd(TestRole):
@ -320,7 +334,14 @@ class TestUserRoleList(TestRole):
# Get the command object to test # Get the command object to test
self.cmd = role.ListUserRole(self.app, None) self.cmd = role.ListUserRole(self.app, None)
def test_user_role_list_no_options(self): def test_user_role_list_no_options_unscoped_token(self):
auth_ref = identity_fakes.fake_auth_ref(
identity_fakes.UNSCOPED_TOKEN,
fake_service=self.fake_service,
)
self.ar_mock = mock.PropertyMock(return_value=auth_ref)
type(self.app.client_manager).auth_ref = self.ar_mock
arglist = [] arglist = []
verifylist = [] verifylist = []
parsed_args = self.check_parser(self.cmd, arglist, verifylist) parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@ -332,11 +353,7 @@ class TestUserRoleList(TestRole):
parsed_args, parsed_args,
) )
def test_user_role_list_no_options_def_creds(self): def test_user_role_list_no_options_scoped_token(self):
auth_ref = self.app.client_manager.auth_ref = mock.MagicMock()
auth_ref.project_id.return_value = identity_fakes.project_id
auth_ref.user_id.return_value = identity_fakes.user_id
arglist = [] arglist = []
verifylist = [] verifylist = []
parsed_args = self.check_parser(self.cmd, arglist, verifylist) parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@ -361,7 +378,14 @@ class TestUserRoleList(TestRole):
), ) ), )
self.assertEqual(datalist, tuple(data)) self.assertEqual(datalist, tuple(data))
def test_user_role_list_project(self): def test_user_role_list_project_unscoped_token(self):
auth_ref = identity_fakes.fake_auth_ref(
identity_fakes.UNSCOPED_TOKEN,
fake_service=self.fake_service,
)
self.ar_mock = mock.PropertyMock(return_value=auth_ref)
type(self.app.client_manager).auth_ref = self.ar_mock
self.projects_mock.get.return_value = fakes.FakeResource( self.projects_mock.get.return_value = fakes.FakeResource(
None, None,
copy.deepcopy(identity_fakes.PROJECT_2), copy.deepcopy(identity_fakes.PROJECT_2),
@ -375,18 +399,26 @@ class TestUserRoleList(TestRole):
] ]
parsed_args = self.check_parser(self.cmd, arglist, verifylist) parsed_args = self.check_parser(self.cmd, arglist, verifylist)
# This argument combination should raise a CommandError # In base command class Lister in cliff, abstract method take_action()
self.assertRaises( # returns a tuple containing the column names and an iterable
exceptions.CommandError, # containing the data to be listed.
self.cmd.take_action, columns, data = self.cmd.take_action(parsed_args)
parsed_args,
self.roles_mock.roles_for_user.assert_called_with(
identity_fakes.user_id,
identity_fakes.PROJECT_2['id'],
) )
def test_user_role_list_project_def_creds(self): self.assertEqual(columns, columns)
auth_ref = self.app.client_manager.auth_ref = mock.MagicMock() datalist = ((
auth_ref.project_id.return_value = identity_fakes.project_id identity_fakes.role_id,
auth_ref.user_id.return_value = identity_fakes.user_id identity_fakes.role_name,
identity_fakes.PROJECT_2['name'],
identity_fakes.user_name,
), )
self.assertEqual(datalist, tuple(data))
def test_user_role_list_project_scoped_token(self):
self.projects_mock.get.return_value = fakes.FakeResource( self.projects_mock.get.return_value = fakes.FakeResource(
None, None,
copy.deepcopy(identity_fakes.PROJECT_2), copy.deepcopy(identity_fakes.PROJECT_2),

View File

@ -24,10 +24,9 @@ class TestToken(identity_fakes.TestIdentityv2):
def setUp(self): def setUp(self):
super(TestToken, self).setUp() super(TestToken, self).setUp()
# Get a shortcut to the Service Catalog Mock # Get a shortcut to the Auth Ref Mock
self.sc_mock = mock.Mock() self.ar_mock = mock.PropertyMock()
self.app.client_manager.auth_ref = mock.Mock() type(self.app.client_manager).auth_ref = self.ar_mock
self.app.client_manager.auth_ref.service_catalog = self.sc_mock
class TestTokenIssue(TestToken): class TestTokenIssue(TestToken):
@ -35,10 +34,15 @@ class TestTokenIssue(TestToken):
def setUp(self): def setUp(self):
super(TestTokenIssue, self).setUp() super(TestTokenIssue, self).setUp()
self.sc_mock.get_token.return_value = identity_fakes.TOKEN
self.cmd = token.IssueToken(self.app, None) self.cmd = token.IssueToken(self.app, None)
def test_token_issue(self): def test_token_issue(self):
auth_ref = identity_fakes.fake_auth_ref(
identity_fakes.TOKEN,
)
self.ar_mock = mock.PropertyMock(return_value=auth_ref)
type(self.app.client_manager).auth_ref = self.ar_mock
arglist = [] arglist = []
verifylist = [] verifylist = []
parsed_args = self.check_parser(self.cmd, arglist, verifylist) parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@ -48,12 +52,10 @@ class TestTokenIssue(TestToken):
# data to be shown. # data to be shown.
columns, data = self.cmd.take_action(parsed_args) columns, data = self.cmd.take_action(parsed_args)
self.sc_mock.get_token.assert_called_with()
collist = ('expires', 'id', 'project_id', 'user_id') collist = ('expires', 'id', 'project_id', 'user_id')
self.assertEqual(collist, columns) self.assertEqual(collist, columns)
datalist = ( datalist = (
identity_fakes.token_expires, auth_ref.expires,
identity_fakes.token_id, identity_fakes.token_id,
identity_fakes.project_id, identity_fakes.project_id,
identity_fakes.user_id, identity_fakes.user_id,
@ -61,8 +63,11 @@ class TestTokenIssue(TestToken):
self.assertEqual(datalist, data) self.assertEqual(datalist, data)
def test_token_issue_with_unscoped_token(self): def test_token_issue_with_unscoped_token(self):
# make sure we return an unscoped token auth_ref = identity_fakes.fake_auth_ref(
self.sc_mock.get_token.return_value = identity_fakes.UNSCOPED_TOKEN identity_fakes.UNSCOPED_TOKEN,
)
self.ar_mock = mock.PropertyMock(return_value=auth_ref)
type(self.app.client_manager).auth_ref = self.ar_mock
arglist = [] arglist = []
verifylist = [] verifylist = []
@ -71,12 +76,14 @@ class TestTokenIssue(TestToken):
# DisplayCommandBase.take_action() returns two tuples # DisplayCommandBase.take_action() returns two tuples
columns, data = self.cmd.take_action(parsed_args) columns, data = self.cmd.take_action(parsed_args)
self.sc_mock.get_token.assert_called_with() collist = (
'expires',
collist = ('expires', 'id', 'user_id') 'id',
'user_id',
)
self.assertEqual(collist, columns) self.assertEqual(collist, columns)
datalist = ( datalist = (
identity_fakes.token_expires, auth_ref.expires,
identity_fakes.token_id, identity_fakes.token_id,
identity_fakes.user_id, identity_fakes.user_id,
) )

View File

@ -13,8 +13,12 @@
# under the License. # under the License.
# #
import copy
import mock import mock
from keystoneauth1 import access
from keystoneauth1 import fixture
from openstackclient.tests import fakes from openstackclient.tests import fakes
from openstackclient.tests import utils from openstackclient.tests import utils
@ -419,6 +423,36 @@ OAUTH_VERIFIER = {
} }
def fake_auth_ref(fake_token, fake_service=None):
"""Create an auth_ref using keystoneauth's fixtures"""
token_copy = copy.deepcopy(fake_token)
token_id = token_copy.pop('id')
token = fixture.V3Token(**token_copy)
# An auth_ref is actually an access info object
auth_ref = access.create(
body=token,
auth_token=token_id,
)
# Create a service catalog
if fake_service:
service = token.add_service(
fake_service['type'],
fake_service['name'],
)
# TODO(dtroyer): Add an 'id' element to KSA's _Service fixure
service['id'] = fake_service['id']
for e in fake_service['endpoints']:
region = e.get('region_id') or e.get('region', '<none>')
service.add_endpoint(
e['interface'],
e['url'],
region=region,
)
return auth_ref
class FakeAuth(object): class FakeAuth(object):
def __init__(self, auth_method_class=None): def __init__(self, auth_method_class=None):

View File

@ -14,6 +14,7 @@
import mock import mock
from openstackclient.identity.v3 import catalog from openstackclient.identity.v3 import catalog
from openstackclient.tests.identity.v3 import fakes as identity_fakes
from openstackclient.tests import utils from openstackclient.tests import utils
@ -50,7 +51,7 @@ class TestCatalog(utils.TestCommand):
super(TestCatalog, self).setUp() super(TestCatalog, self).setUp()
self.sc_mock = mock.MagicMock() self.sc_mock = mock.MagicMock()
self.sc_mock.service_catalog.get_data.return_value = [ self.sc_mock.service_catalog.catalog.return_value = [
self.fake_service, self.fake_service,
] ]
@ -69,6 +70,13 @@ class TestCatalogList(TestCatalog):
self.cmd = catalog.ListCatalog(self.app, None) self.cmd = catalog.ListCatalog(self.app, None)
def test_catalog_list(self): def test_catalog_list(self):
auth_ref = identity_fakes.fake_auth_ref(
identity_fakes.TOKEN_WITH_PROJECT_ID,
fake_service=self.fake_service,
)
self.ar_mock = mock.PropertyMock(return_value=auth_ref)
type(self.app.client_manager).auth_ref = self.ar_mock
arglist = [] arglist = []
verifylist = [] verifylist = []
parsed_args = self.check_parser(self.cmd, arglist, verifylist) parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@ -77,7 +85,6 @@ class TestCatalogList(TestCatalog):
# returns a tuple containing the column names and an iterable # returns a tuple containing the column names and an iterable
# containing the data to be listed. # containing the data to be listed.
columns, data = self.cmd.take_action(parsed_args) columns, data = self.cmd.take_action(parsed_args)
self.sc_mock.service_catalog.get_data.assert_called_with()
collist = ('Name', 'Type', 'Endpoints') collist = ('Name', 'Type', 'Endpoints')
self.assertEqual(collist, columns) self.assertEqual(collist, columns)
@ -101,6 +108,13 @@ class TestCatalogShow(TestCatalog):
self.cmd = catalog.ShowCatalog(self.app, None) self.cmd = catalog.ShowCatalog(self.app, None)
def test_catalog_show(self): def test_catalog_show(self):
auth_ref = identity_fakes.fake_auth_ref(
identity_fakes.TOKEN_WITH_PROJECT_ID,
fake_service=self.fake_service,
)
self.ar_mock = mock.PropertyMock(return_value=auth_ref)
type(self.app.client_manager).auth_ref = self.ar_mock
arglist = [ arglist = [
'compute', 'compute',
] ]
@ -113,7 +127,6 @@ class TestCatalogShow(TestCatalog):
# returns a two-part tuple with a tuple of column names and a tuple of # returns a two-part tuple with a tuple of column names and a tuple of
# data to be shown. # data to be shown.
columns, data = self.cmd.take_action(parsed_args) columns, data = self.cmd.take_action(parsed_args)
self.sc_mock.service_catalog.get_data.assert_called_with()
collist = ('endpoints', 'id', 'name', 'type') collist = ('endpoints', 'id', 'name', 'type')
self.assertEqual(collist, columns) self.assertEqual(collist, columns)

View File

@ -24,10 +24,9 @@ class TestToken(identity_fakes.TestIdentityv3):
def setUp(self): def setUp(self):
super(TestToken, self).setUp() super(TestToken, self).setUp()
# Get a shortcut to the Service Catalog Mock # Get a shortcut to the Auth Ref Mock
self.sc_mock = mock.Mock() self.ar_mock = mock.PropertyMock()
self.app.client_manager.auth_ref = mock.Mock() type(self.app.client_manager).auth_ref = self.ar_mock
self.app.client_manager.auth_ref.service_catalog = self.sc_mock
class TestTokenIssue(TestToken): class TestTokenIssue(TestToken):
@ -38,23 +37,25 @@ class TestTokenIssue(TestToken):
self.cmd = token.IssueToken(self.app, None) self.cmd = token.IssueToken(self.app, None)
def test_token_issue_with_project_id(self): def test_token_issue_with_project_id(self):
auth_ref = identity_fakes.fake_auth_ref(
identity_fakes.TOKEN_WITH_PROJECT_ID,
)
self.ar_mock = mock.PropertyMock(return_value=auth_ref)
type(self.app.client_manager).auth_ref = self.ar_mock
arglist = [] arglist = []
verifylist = [] verifylist = []
parsed_args = self.check_parser(self.cmd, arglist, verifylist) parsed_args = self.check_parser(self.cmd, arglist, verifylist)
self.sc_mock.get_token.return_value = \
identity_fakes.TOKEN_WITH_PROJECT_ID
# In base command class ShowOne in cliff, abstract method take_action() # In base command class ShowOne in cliff, abstract method take_action()
# returns a two-part tuple with a tuple of column names and a tuple of # returns a two-part tuple with a tuple of column names and a tuple of
# data to be shown. # data to be shown.
columns, data = self.cmd.take_action(parsed_args) columns, data = self.cmd.take_action(parsed_args)
self.sc_mock.get_token.assert_called_with()
collist = ('expires', 'id', 'project_id', 'user_id') collist = ('expires', 'id', 'project_id', 'user_id')
self.assertEqual(collist, columns) self.assertEqual(collist, columns)
datalist = ( datalist = (
identity_fakes.token_expires, auth_ref.expires,
identity_fakes.token_id, identity_fakes.token_id,
identity_fakes.project_id, identity_fakes.project_id,
identity_fakes.user_id, identity_fakes.user_id,
@ -62,45 +63,53 @@ class TestTokenIssue(TestToken):
self.assertEqual(datalist, data) self.assertEqual(datalist, data)
def test_token_issue_with_domain_id(self): def test_token_issue_with_domain_id(self):
auth_ref = identity_fakes.fake_auth_ref(
identity_fakes.TOKEN_WITH_DOMAIN_ID,
)
self.ar_mock = mock.PropertyMock(return_value=auth_ref)
type(self.app.client_manager).auth_ref = self.ar_mock
arglist = [] arglist = []
verifylist = [] verifylist = []
parsed_args = self.check_parser(self.cmd, arglist, verifylist) parsed_args = self.check_parser(self.cmd, arglist, verifylist)
self.sc_mock.get_token.return_value = \
identity_fakes.TOKEN_WITH_DOMAIN_ID
# In base command class ShowOne in cliff, abstract method take_action() # In base command class ShowOne in cliff, abstract method take_action()
# returns a two-part tuple with a tuple of column names and a tuple of # returns a two-part tuple with a tuple of column names and a tuple of
# data to be shown. # data to be shown.
columns, data = self.cmd.take_action(parsed_args) columns, data = self.cmd.take_action(parsed_args)
self.sc_mock.get_token.assert_called_with()
collist = ('domain_id', 'expires', 'id', 'user_id') collist = ('domain_id', 'expires', 'id', 'user_id')
self.assertEqual(collist, columns) self.assertEqual(collist, columns)
datalist = ( datalist = (
identity_fakes.domain_id, identity_fakes.domain_id,
identity_fakes.token_expires, auth_ref.expires,
identity_fakes.token_id, identity_fakes.token_id,
identity_fakes.user_id, identity_fakes.user_id,
) )
self.assertEqual(datalist, data) self.assertEqual(datalist, data)
def test_token_issue_with_unscoped(self): def test_token_issue_with_unscoped(self):
auth_ref = identity_fakes.fake_auth_ref(
identity_fakes.UNSCOPED_TOKEN,
)
self.ar_mock = mock.PropertyMock(return_value=auth_ref)
type(self.app.client_manager).auth_ref = self.ar_mock
arglist = [] arglist = []
verifylist = [] verifylist = []
parsed_args = self.check_parser(self.cmd, arglist, verifylist) parsed_args = self.check_parser(self.cmd, arglist, verifylist)
self.sc_mock.get_token.return_value = \
identity_fakes.UNSCOPED_TOKEN
# DisplayCommandBase.take_action() returns two tuples # DisplayCommandBase.take_action() returns two tuples
columns, data = self.cmd.take_action(parsed_args) columns, data = self.cmd.take_action(parsed_args)
self.sc_mock.get_token.assert_called_with() collist = (
'expires',
collist = ('expires', 'id', 'user_id') 'id',
'user_id',
)
self.assertEqual(collist, columns) self.assertEqual(collist, columns)
datalist = ( datalist = (
identity_fakes.token_expires, auth_ref.expires,
identity_fakes.token_id, identity_fakes.token_id,
identity_fakes.user_id, identity_fakes.user_id,
) )

View File

@ -26,7 +26,7 @@ packages =
console_scripts = console_scripts =
openstack = openstackclient.shell:main openstack = openstackclient.shell:main
keystoneclient.auth.plugin = keystoneauth1.plugin =
token_endpoint = openstackclient.api.auth_plugin:TokenEndpoint token_endpoint = openstackclient.api.auth_plugin:TokenEndpoint
osc_password = openstackclient.api.auth_plugin:OSCGenericPassword osc_password = openstackclient.api.auth_plugin:OSCGenericPassword