diff --git a/openstackclient/api/auth.py b/openstackclient/api/auth.py index 66272e4..2cde7d4 100644 --- a/openstackclient/api/auth.py +++ b/openstackclient/api/auth.py @@ -135,8 +135,12 @@ def build_auth_params(auth_plugin_name, cmd_options): return (auth_plugin_class, auth_params) -def check_valid_auth_options(options, auth_plugin_name): - """Perform basic option checking, provide helpful error messages""" +def check_valid_auth_options(options, auth_plugin_name, required_scope=True): + """Perform basic option checking, provide helpful error messages. + + :param required_scope: indicate whether a scoped token is required + + """ msg = '' if auth_plugin_name.endswith('password'): @@ -146,7 +150,8 @@ def check_valid_auth_options(options, auth_plugin_name): if not options.auth.get('auth_url', None): msg += _('Set an authentication URL, with --os-auth-url,' ' OS_AUTH_URL or auth.auth_url\n') - if (not options.auth.get('project_id', None) and not + if (required_scope and not + options.auth.get('project_id', None) and not options.auth.get('domain_id', None) and not options.auth.get('domain_name', None) and not options.auth.get('project_name', None) and not diff --git a/openstackclient/common/clientmanager.py b/openstackclient/common/clientmanager.py index dce1972..48b3aca 100644 --- a/openstackclient/common/clientmanager.py +++ b/openstackclient/common/clientmanager.py @@ -113,19 +113,35 @@ class ClientManager(object): root_logger = logging.getLogger('') LOG.setLevel(root_logger.getEffectiveLevel()) - def setup_auth(self): + # NOTE(gyee): use this flag to indicate whether auth setup has already + # been completed. If so, do not perform auth setup again. The reason + # we need this flag is that we want to be able to perform auth setup + # outside of auth_ref as auth_ref itself is a property. We can not + # retrofit auth_ref to optionally skip scope check. Some operations + # do not require a scoped token. In those cases, we call setup_auth + # prior to dereferrencing auth_ref. + self._auth_setup_completed = False + + def setup_auth(self, required_scope=True): """Set up authentication + :param required_scope: indicate whether a scoped token is required + This is deferred until authentication is actually attempted because it gets in the way of things that do not require auth. """ + if self._auth_setup_completed: + return + # If no auth type is named by the user, select one based on # the supplied options self.auth_plugin_name = auth.select_auth_plugin(self._cli_options) # Basic option checking to avoid unhelpful error messages - auth.check_valid_auth_options(self._cli_options, self.auth_plugin_name) + auth.check_valid_auth_options(self._cli_options, + self.auth_plugin_name, + required_scope=required_scope) # Horrible hack alert...must handle prompt for null password if # password auth is requested. @@ -180,6 +196,8 @@ class ClientManager(object): user_agent=USER_AGENT, ) + self._auth_setup_completed = True + return @property diff --git a/openstackclient/shell.py b/openstackclient/shell.py index 137446e..dfec40a 100644 --- a/openstackclient/shell.py +++ b/openstackclient/shell.py @@ -353,6 +353,9 @@ class OpenStackShell(app.App): cmd.__class__.__name__, ) if cmd.auth_required: + if hasattr(cmd, 'required_scope'): + # let the command decide whether we need a scoped token + self.client_manager.setup_auth(cmd.required_scope) # Trigger the Identity client to initialize self.client_manager.auth_ref return diff --git a/openstackclient/tests/common/test_clientmanager.py b/openstackclient/tests/common/test_clientmanager.py index 523f79a..ef46f61 100644 --- a/openstackclient/tests/common/test_clientmanager.py +++ b/openstackclient/tests/common/test_clientmanager.py @@ -325,3 +325,28 @@ class TestClientManager(utils.TestCase): exc.CommandError, client_manager.setup_auth, ) + + @mock.patch('openstackclient.api.auth.check_valid_auth_options') + def test_client_manager_auth_setup_once(self, check_auth_options_func): + client_manager = clientmanager.ClientManager( + cli_options=FakeOptions( + auth=dict( + auth_url=fakes.AUTH_URL, + username=fakes.USERNAME, + password=fakes.PASSWORD, + project_name=fakes.PROJECT_NAME, + ), + ), + api_version=API_VERSION, + verify=False, + ) + self.assertFalse(client_manager._auth_setup_completed) + client_manager.setup_auth() + self.assertTrue(check_auth_options_func.called) + self.assertTrue(client_manager._auth_setup_completed) + + # now make sure we don't do auth setup the second time around + # by checking whether check_valid_auth_options() gets called again + check_auth_options_func.reset_mock() + client_manager.auth_ref + check_auth_options_func.assert_not_called()