Prompt for password on CLI if not provided

load_from_argparse_arguments is very specifically for use with argparse.
We can therefore safely prompt for a password from the user if none is
provided and it won't affect config options or other loading mechanisms.

Change-Id: Ib76743b768c5f0eef756184f1da49613423298f0
This commit is contained in:
Jamie Lennox 2015-04-15 09:36:10 +10:00
parent 39b7f963f5
commit 17d51f771e
8 changed files with 136 additions and 8 deletions

View File

@ -82,3 +82,11 @@ class Password(base.BaseGenericPlugin):
options = super(Password, cls).get_options()
options.extend(get_options())
return options
@classmethod
def load_from_argparse_arguments(cls, namespace, **kwargs):
if not (kwargs.get('password') or namespace.os_password):
kwargs['password'] = utils.prompt_user_password()
return super(Password, cls).load_from_argparse_arguments(namespace,
**kwargs)

View File

@ -144,6 +144,14 @@ class Password(Auth):
return {'passwordCredentials': auth}
@classmethod
def load_from_argparse_arguments(cls, namespace, **kwargs):
if not (kwargs.get('password') or namespace.os_password):
kwargs['password'] = utils.prompt_user_password()
return super(Password, cls).load_from_argparse_arguments(namespace,
**kwargs)
@classmethod
def get_options(cls):
options = super(Password, cls).get_options()

View File

@ -13,6 +13,7 @@
from oslo_config import cfg
from keystoneclient.auth.identity.v3 import base
from keystoneclient import utils
__all__ = ['PasswordMethod', 'Password']
@ -86,3 +87,11 @@ class Password(base.AuthConstructor):
])
return options
@classmethod
def load_from_argparse_arguments(cls, namespace, **kwargs):
if not (kwargs.get('password') or namespace.os_password):
kwargs['password'] = utils.prompt_user_password()
return super(Password, cls).load_from_argparse_arguments(namespace,
**kwargs)

View File

@ -19,7 +19,6 @@
from __future__ import print_function
import argparse
import getpass
import logging
import os
import sys
@ -296,13 +295,8 @@ class OpenStackIdentityShell(object):
'--os-username or env[OS_USERNAME]')
if not args.os_password:
# No password, If we've got a tty, try prompting for it
if hasattr(sys.stdin, 'isatty') and sys.stdin.isatty():
# Check for Ctl-D
try:
args.os_password = getpass.getpass('OS Password: ')
except EOFError:
pass
args.os_password = utils.prompt_user_password()
# No password because we didn't have a tty or the
# user Ctl-D when prompted?
if not args.os_password:

View File

@ -10,9 +10,12 @@
# License for the specific language governing permissions and limitations
# under the License.
import argparse
import copy
import uuid
import mock
from keystoneclient.auth.identity import v2
from keystoneclient import exceptions
from keystoneclient import session
@ -294,3 +297,28 @@ class V2IdentityPlugin(utils.TestCase):
def test_password_with_no_user_id_or_name(self):
self.assertRaises(TypeError,
v2.Password, self.TEST_URL, password=self.TEST_PASS)
@mock.patch('sys.stdin', autospec=True)
def test_prompt_password(self, mock_stdin):
parser = argparse.ArgumentParser()
v2.Password.register_argparse_arguments(parser)
username = uuid.uuid4().hex
auth_url = uuid.uuid4().hex
tenant_id = uuid.uuid4().hex
password = uuid.uuid4().hex
opts = parser.parse_args(['--os-username', username,
'--os-auth-url', auth_url,
'--os-tenant-id', tenant_id])
with mock.patch('getpass.getpass') as mock_getpass:
mock_getpass.return_value = password
mock_stdin.isatty = lambda: True
plugin = v2.Password.load_from_argparse_arguments(opts)
self.assertEqual(auth_url, plugin.auth_url)
self.assertEqual(username, plugin.username)
self.assertEqual(tenant_id, plugin.tenant_id)
self.assertEqual(password, plugin.password)

View File

@ -10,9 +10,12 @@
# License for the specific language governing permissions and limitations
# under the License.
import argparse
import copy
import uuid
import mock
from keystoneclient import access
from keystoneclient.auth.identity import v3
from keystoneclient.auth.identity.v3 import base as v3_base
@ -531,3 +534,32 @@ class V3IdentityPlugin(utils.TestCase):
s = session.Session()
self.assertRaises(exceptions.AuthorizationFailure, a.get_auth_ref, s)
@mock.patch('sys.stdin', autospec=True)
def test_prompt_password(self, mock_stdin):
parser = argparse.ArgumentParser()
v3.Password.register_argparse_arguments(parser)
username = uuid.uuid4().hex
user_domain_id = uuid.uuid4().hex
auth_url = uuid.uuid4().hex
project_id = uuid.uuid4().hex
password = uuid.uuid4().hex
opts = parser.parse_args(['--os-username', username,
'--os-auth-url', auth_url,
'--os-user-domain-id', user_domain_id,
'--os-project-id', project_id])
with mock.patch('getpass.getpass') as mock_getpass:
mock_getpass.return_value = password
mock_stdin.isatty = lambda: True
plugin = v3.Password.load_from_argparse_arguments(opts)
self.assertEqual(auth_url, plugin.auth_url)
self.assertEqual(username, plugin.auth_methods[0].username)
self.assertEqual(project_id, plugin.project_id)
self.assertEqual(user_domain_id,
plugin.auth_methods[0].user_domain_id)
self.assertEqual(password, plugin.auth_methods[0].password)

View File

@ -10,8 +10,11 @@
# License for the specific language governing permissions and limitations
# under the License.
import argparse
import uuid
import mock
from keystoneclient.auth.identity.generic import password
from keystoneclient.auth.identity import v2
from keystoneclient.auth.identity import v3
@ -66,3 +69,31 @@ class PasswordTests(utils.GenericPluginTestCase):
def test_symbols(self):
self.assertIs(v3.Password, v3_password.Password)
self.assertIs(v3.PasswordMethod, v3_password.PasswordMethod)
@mock.patch('sys.stdin', autospec=True)
def test_prompt_password(self, mock_stdin):
parser = argparse.ArgumentParser()
self.PLUGIN_CLASS.register_argparse_arguments(parser)
username = uuid.uuid4().hex
user_domain_id = uuid.uuid4().hex
auth_url = uuid.uuid4().hex
project_id = uuid.uuid4().hex
password = uuid.uuid4().hex
opts = parser.parse_args(['--os-username', username,
'--os-auth-url', auth_url,
'--os-user-domain-id', user_domain_id,
'--os-project-id', project_id])
with mock.patch('getpass.getpass') as mock_getpass:
mock_getpass.return_value = password
mock_stdin.isatty = lambda: True
plugin = self.PLUGIN_CLASS.load_from_argparse_arguments(opts)
self.assertEqual(auth_url, plugin.auth_url)
self.assertEqual(username, plugin._username)
self.assertEqual(project_id, plugin._project_id)
self.assertEqual(user_domain_id, plugin._user_domain_id)
self.assertEqual(password, plugin._password)

View File

@ -147,6 +147,24 @@ def hash_signed_token(signed_text, mode='md5'):
return hash_.hexdigest()
def prompt_user_password():
"""Prompt user for a password
Prompt for a password if stdin is a tty.
"""
password = None
# If stdin is a tty, try prompting for the password
if hasattr(sys.stdin, 'isatty') and sys.stdin.isatty():
# Check for Ctl-D
try:
password = getpass.getpass('Password: ')
except EOFError:
pass
return password
def prompt_for_password():
"""Prompt user for password if not provided so the password
doesn't show up in the bash history.