From bec206fa0a0214d856259661c5e32086f33d2f62 Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Mon, 29 Aug 2016 11:07:49 -0500 Subject: [PATCH] Fix auth prompt brokenness We start by fixing this in the already-present OSC_Config class so OSC can move forward. This change needs to get ported down into os-client-config in the near future, maybe even soon enough to make the client library freeze this week. * Add the pw-func argument to the OSC_Config (or OpenStackConfig) __init__() * When looping through the auth options from the KSA plugin look for any that have a prompt defined and do not have a value already, so ask for one. Closes-bug: #1617384 Change-Id: Ic86d56b8a6844516292fb74513712b486fec4442 --- openstackclient/common/client_config.py | 43 +++++++++++++++++ openstackclient/common/clientmanager.py | 2 - openstackclient/shell.py | 2 +- openstackclient/tests/test_shell_integ.py | 58 +++++++++++++++++++++++ 4 files changed, 102 insertions(+), 3 deletions(-) diff --git a/openstackclient/common/client_config.py b/openstackclient/common/client_config.py index bbcb34eb7b..bc2314385b 100644 --- a/openstackclient/common/client_config.py +++ b/openstackclient/common/client_config.py @@ -25,6 +25,40 @@ LOG = logging.getLogger(__name__) # before auth plugins are loaded class OSC_Config(OpenStackConfig): + # TODO(dtroyer): Once os-client-config with pw_func argument is in + # global-requirements we can remove __init()__ + def __init__( + self, + config_files=None, + vendor_files=None, + override_defaults=None, + force_ipv4=None, + envvar_prefix=None, + secure_files=None, + pw_func=None, + ): + ret = super(OSC_Config, self).__init__( + config_files=config_files, + vendor_files=vendor_files, + override_defaults=override_defaults, + force_ipv4=force_ipv4, + envvar_prefix=envvar_prefix, + secure_files=secure_files, + ) + + # NOTE(dtroyer): This will be pushed down into os-client-config + # The default is there is no callback, the calling + # application must specify what to use, typically + # it will be osc_lib.shell.prompt_for_password() + if '_pw_callback' not in vars(self): + # Set the default if it doesn't already exist + self._pw_callback = None + if pw_func is not None: + # Set the passed in value + self._pw_callback = pw_func + + return ret + def _auth_select_default_plugin(self, config): """Select a default plugin based on supplied arguments @@ -183,4 +217,13 @@ class OSC_Config(OpenStackConfig): else: config['auth'][p_opt.dest] = winning_value + # See if this needs a prompting + if ( + 'prompt' in vars(p_opt) and + p_opt.prompt is not None and + p_opt.dest not in config['auth'] and + self._pw_callback is not None + ): + config['auth'][p_opt.dest] = self._pw_callback(p_opt.prompt) + return config diff --git a/openstackclient/common/clientmanager.py b/openstackclient/common/clientmanager.py index ccfde2d0d3..9097543b14 100644 --- a/openstackclient/common/clientmanager.py +++ b/openstackclient/common/clientmanager.py @@ -44,12 +44,10 @@ class ClientManager(clientmanager.ClientManager): self, cli_options=None, api_version=None, - pw_func=None, ): super(ClientManager, self).__init__( cli_options=cli_options, api_version=api_version, - pw_func=pw_func, ) # TODO(dtroyer): For compatibility; mark this for removal when plugin diff --git a/openstackclient/shell.py b/openstackclient/shell.py index da58b63bf7..26147be999 100644 --- a/openstackclient/shell.py +++ b/openstackclient/shell.py @@ -145,6 +145,7 @@ class OpenStackShell(shell.OpenStackShell): 'interface': None, 'auth_type': self._auth_type, }, + pw_func=shell.prompt_for_password, ) except (IOError, OSError) as e: self.log.critical("Could not read clouds.yaml configuration file") @@ -162,7 +163,6 @@ class OpenStackShell(shell.OpenStackShell): self.client_manager = clientmanager.ClientManager( cli_options=self.cloud, api_version=self.api_version, - pw_func=shell.prompt_for_password, ) diff --git a/openstackclient/tests/test_shell_integ.py b/openstackclient/tests/test_shell_integ.py index bc5f1ae51a..d50113b2ff 100644 --- a/openstackclient/tests/test_shell_integ.py +++ b/openstackclient/tests/test_shell_integ.py @@ -354,6 +354,64 @@ class TestShellCliV3Integ(TestShellInteg): self.assertFalse(self.requests_mock.request_history[0].verify) +class TestShellCliV3Prompt(TestShellInteg): + + def setUp(self): + super(TestShellCliV3Prompt, self).setUp() + env = { + "OS_AUTH_URL": V3_AUTH_URL, + "OS_PROJECT_DOMAIN_ID": test_shell.DEFAULT_PROJECT_DOMAIN_ID, + "OS_USER_DOMAIN_ID": test_shell.DEFAULT_USER_DOMAIN_ID, + "OS_USERNAME": test_shell.DEFAULT_USERNAME, + "OS_IDENTITY_API_VERSION": "3", + } + self.useFixture(osc_lib_utils.EnvFixture(copy.deepcopy(env))) + + self.token = ksa_fixture.V3Token( + project_domain_id=test_shell.DEFAULT_PROJECT_DOMAIN_ID, + user_domain_id=test_shell.DEFAULT_USER_DOMAIN_ID, + user_name=test_shell.DEFAULT_USERNAME, + ) + + # Set up the v3 auth routes + self.requests_mock.register_uri( + 'GET', + V3_AUTH_URL, + json=V3_VERSION_RESP, + status_code=200, + ) + self.requests_mock.register_uri( + 'POST', + V3_AUTH_URL + 'auth/tokens', + json=self.token, + status_code=200, + ) + + @mock.patch("osc_lib.shell.prompt_for_password") + def test_shell_callback(self, mock_prompt): + mock_prompt.return_value = "qaz" + _shell = shell.OpenStackShell() + _shell.run("configuration show".split()) + + # Check general calls + self.assertEqual(len(self.requests_mock.request_history), 2) + + # Check password callback set correctly + self.assertEqual( + mock_prompt, + _shell.cloud._openstack_config._pw_callback + ) + + # Check auth request + auth_req = self.requests_mock.request_history[1].json() + + # Check returned password from prompt function + self.assertEqual( + "qaz", + auth_req['auth']['identity']['password']['user']['password'], + ) + + class TestShellCliPrecedence(TestShellInteg): """Validate option precedence rules without clouds.yaml