Merge "Add option for user to enter password"

This commit is contained in:
Zuul 2018-06-30 00:51:05 +00:00 committed by Gerrit Code Review
commit c2c5af603f
3 changed files with 95 additions and 3 deletions

View File

@ -139,6 +139,10 @@ swift optional arguments
compression should be disabled by default by the compression should be disabled by default by the
system SSL library. system SSL library.
``--prompt``
Prompt user to enter a password which overrides any password supplied via
``--key``, ``--os-password`` or environment variables.
Authentication Authentication
~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~

View File

@ -17,11 +17,13 @@
from __future__ import print_function, unicode_literals from __future__ import print_function, unicode_literals
import argparse import argparse
import getpass
import io import io
import json import json
import logging import logging
import signal import signal
import socket import socket
import warnings
from os import environ, walk, _exit as os_exit from os import environ, walk, _exit as os_exit
from os.path import isfile, isdir, join from os.path import isfile, isdir, join
@ -1410,6 +1412,30 @@ class HelpFormatter(argparse.HelpFormatter):
return action.dest return action.dest
def prompt_for_password():
"""
Prompt the user for a password.
:raise SystemExit: if a password cannot be entered without it being echoed
to the terminal.
:return: the entered password.
"""
with warnings.catch_warnings():
warnings.filterwarnings('error', category=getpass.GetPassWarning,
append=True)
try:
# temporarily set signal handling back to default to avoid user
# Ctrl-c leaving terminal in weird state
signal.signal(signal.SIGINT, signal.SIG_DFL)
return getpass.getpass()
except EOFError:
return None
except getpass.GetPassWarning:
exit('Input stream incompatible with --prompt option')
finally:
signal.signal(signal.SIGINT, immediate_exit)
def parse_args(parser, args, enforce_requires=True): def parse_args(parser, args, enforce_requires=True):
options, args = parser.parse_known_args(args or ['-h']) options, args = parser.parse_known_args(args or ['-h'])
options = vars(options) options = vars(options)
@ -1435,6 +1461,10 @@ def parse_args(parser, args, enforce_requires=True):
if args and args[0] == 'tempurl': if args and args[0] == 'tempurl':
return options, args return options, args
# do this before process_options sets default auth version
if enforce_requires and options['prompt']:
options['key'] = options['os_password'] = prompt_for_password()
# Massage auth version; build out os_options subdict # Massage auth version; build out os_options subdict
process_options(options) process_options(options)
@ -1506,6 +1536,7 @@ def main(arguments=None):
[--os-key <client-certificate-key-file>] [--os-key <client-certificate-key-file>]
[--no-ssl-compression] [--no-ssl-compression]
[--force-auth-retry] [--force-auth-retry]
[--prompt]
<subcommand> [--help] [<subcommand options>] <subcommand> [--help] [<subcommand options>]
Command-line interface to the OpenStack Swift API. Command-line interface to the OpenStack Swift API.
@ -1620,6 +1651,12 @@ Examples:
default=False, default=False,
help='Force a re-auth attempt on ' help='Force a re-auth attempt on '
'any error other than 401 unauthorized') 'any error other than 401 unauthorized')
parser.add_argument('--prompt',
action='store_true', dest='prompt',
default=False,
help='Prompt user to enter a password which overrides '
'any password supplied via --key, --os-password '
'or environment variables.')
os_grp = parser.add_argument_group("OpenStack authentication options") os_grp = parser.add_argument_group("OpenStack authentication options")
os_grp.add_argument('--os-username', os_grp.add_argument('--os-username',

View File

@ -13,8 +13,10 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
from __future__ import unicode_literals from __future__ import unicode_literals
from genericpath import getmtime from genericpath import getmtime
import getpass
import hashlib import hashlib
import json import json
import logging import logging
@ -2283,17 +2285,66 @@ class TestParsing(TestBase):
os_opts = {"password": "secret", os_opts = {"password": "secret",
"auth_url": "http://example.com:5000/v3"} "auth_url": "http://example.com:5000/v3"}
args = _make_args("stat", opts, os_opts) args = _make_args("stat", opts, os_opts)
self.assertRaises(SystemExit, swiftclient.shell.main, args) with self.assertRaises(SystemExit) as cm:
swiftclient.shell.main(args)
self.assertIn(
'Auth version 3 requires either OS_USERNAME or OS_USER_ID',
str(cm.exception))
os_opts = {"username": "user", os_opts = {"username": "user",
"auth_url": "http://example.com:5000/v3"} "auth_url": "http://example.com:5000/v3"}
args = _make_args("stat", opts, os_opts) args = _make_args("stat", opts, os_opts)
self.assertRaises(SystemExit, swiftclient.shell.main, args) with self.assertRaises(SystemExit) as cm:
swiftclient.shell.main(args)
self.assertIn('Auth version 3 requires OS_PASSWORD', str(cm.exception))
os_opts = {"username": "user", os_opts = {"username": "user",
"password": "secret"} "password": "secret"}
args = _make_args("stat", opts, os_opts) args = _make_args("stat", opts, os_opts)
self.assertRaises(SystemExit, swiftclient.shell.main, args) with self.assertRaises(SystemExit) as cm:
swiftclient.shell.main(args)
self.assertIn('Auth version 3 requires OS_AUTH_URL', str(cm.exception))
def test_password_prompt(self):
def do_test(opts, os_opts, auth_version):
args = _make_args("stat", opts, os_opts)
result = [None, None]
fake_command = self._make_fake_command(result)
with mock.patch('swiftclient.shell.st_stat', fake_command):
with mock.patch('getpass.getpass',
return_value='input_pwd') as mock_getpass:
swiftclient.shell.main(args)
mock_getpass.assert_called_once_with()
self.assertEqual('input_pwd', result[0]['key'])
self.assertEqual('input_pwd', result[0]['os_password'])
# ctrl-D
with self.assertRaises(SystemExit) as cm:
with mock.patch('swiftclient.shell.st_stat', fake_command):
with mock.patch('getpass.getpass',
side_effect=EOFError) as mock_getpass:
swiftclient.shell.main(args)
mock_getpass.assert_called_once_with()
self.assertIn(
'Auth version %s requires' % auth_version, str(cm.exception))
# force getpass to think it needs to use raw input
with self.assertRaises(SystemExit) as cm:
with mock.patch('getpass.getpass', getpass.fallback_getpass):
swiftclient.shell.main(args)
self.assertIn(
'Input stream incompatible', str(cm.exception))
opts = {"prompt": None, "user": "bob", "key": "secret",
"auth": "http://example.com:8080/auth/v1.0"}
do_test(opts, {}, '1.0')
os_opts = {"username": "user",
"password": "secret",
"auth_url": "http://example.com:5000/v3"}
opts = {"auth_version": "2.0", "prompt": None}
do_test(opts, os_opts, '2.0')
opts = {"auth_version": "3", "prompt": None}
do_test(opts, os_opts, '3')
def test_no_tenant_name_or_id_v2(self): def test_no_tenant_name_or_id_v2(self):
os_opts = {"password": "secret", os_opts = {"password": "secret",