From 033f27fe4dc4455c2f07978a273fd65faa653b67 Mon Sep 17 00:00:00 2001 From: Terry Howe Date: Wed, 19 Feb 2014 19:30:56 -0700 Subject: [PATCH] Add ability to prompt for passwords for user create and set * Add get_password method to the utilities * Add --password-prompt option * Call the get_password method if a prompt is requested * Various tests Change-Id: I1786ad531e2a2fbcc21b8bc86aac0ccd7985995a Closes-Bug: 1100116 --- openstackclient/common/utils.py | 16 ++++ openstackclient/identity/v2_0/user.py | 17 ++++ openstackclient/identity/v3/user.py | 17 ++++ openstackclient/tests/common/test_utils.py | 59 +++++++++++++ .../tests/identity/v2_0/test_user.py | 73 ++++++++++++++++ .../tests/identity/v3/test_user.py | 87 +++++++++++++++++++ 6 files changed, 269 insertions(+) create mode 100644 openstackclient/tests/common/test_utils.py diff --git a/openstackclient/common/utils.py b/openstackclient/common/utils.py index 94ea22253..7cd877ef6 100644 --- a/openstackclient/common/utils.py +++ b/openstackclient/common/utils.py @@ -15,6 +15,7 @@ """Common client utilities""" +import getpass import logging import os import six @@ -229,3 +230,18 @@ def get_effective_log_level(): for handler in root_log.handlers: min_log_lvl = min(min_log_lvl, handler.level) return min_log_lvl + + +def get_password(stdin): + if hasattr(stdin, 'isatty') and stdin.isatty(): + try: + while True: + first_pass = getpass.getpass("User password: ") + second_pass = getpass.getpass("Repeat user password: ") + if first_pass == second_pass: + return first_pass + print("The passwords entered were not the same") + except EOFError: # Ctl-D + raise exceptions.CommandError("Error reading password.") + raise exceptions.CommandError("There was a request to be prompted for a" + " password and a terminal was not detected.") diff --git a/openstackclient/identity/v2_0/user.py b/openstackclient/identity/v2_0/user.py index 371c45a99..abcafdd01 100644 --- a/openstackclient/identity/v2_0/user.py +++ b/openstackclient/identity/v2_0/user.py @@ -42,6 +42,12 @@ class CreateUser(show.ShowOne): metavar='', help='New user password', ) + parser.add_argument( + '--password-prompt', + dest="password_prompt", + action="store_true", + help='Prompt interactively for password', + ) parser.add_argument( '--email', metavar='', @@ -80,6 +86,8 @@ class CreateUser(show.ShowOne): enabled = True if parsed_args.disable: enabled = False + if parsed_args.password_prompt: + parsed_args.password = utils.get_password(self.app.stdin) user = identity_client.users.create( parsed_args.name, @@ -224,6 +232,12 @@ class SetUser(command.Command): metavar='', help='New user password', ) + parser.add_argument( + '--password-prompt', + dest="password_prompt", + action="store_true", + help='Prompt interactively for password', + ) parser.add_argument( '--email', metavar='', @@ -251,6 +265,9 @@ class SetUser(command.Command): self.log.debug('take_action(%s)' % parsed_args) identity_client = self.app.client_manager.identity + if parsed_args.password_prompt: + parsed_args.password = utils.get_password(self.app.stdin) + if (not parsed_args.name and not parsed_args.name and not parsed_args.password diff --git a/openstackclient/identity/v3/user.py b/openstackclient/identity/v3/user.py index 54ffe561a..7e710ac01 100644 --- a/openstackclient/identity/v3/user.py +++ b/openstackclient/identity/v3/user.py @@ -43,6 +43,12 @@ class CreateUser(show.ShowOne): metavar='', help='New user password', ) + parser.add_argument( + '--password-prompt', + dest="password_prompt", + action="store_true", + help='Prompt interactively for password', + ) parser.add_argument( '--email', metavar='', @@ -97,6 +103,8 @@ class CreateUser(show.ShowOne): enabled = True if parsed_args.disable: enabled = False + if parsed_args.password_prompt: + parsed_args.password = utils.get_password(self.app.stdin) user = identity_client.users.create( parsed_args.name, @@ -273,6 +281,12 @@ class SetUser(command.Command): metavar='', help='New user password', ) + parser.add_argument( + '--password-prompt', + dest="password_prompt", + action="store_true", + help='Prompt interactively for password', + ) parser.add_argument( '--email', metavar='', @@ -310,6 +324,9 @@ class SetUser(command.Command): self.log.debug('take_action(%s)' % parsed_args) identity_client = self.app.client_manager.identity + if parsed_args.password_prompt: + parsed_args.password = utils.get_password(self.app.stdin) + if (not parsed_args.name and not parsed_args.name and not parsed_args.password diff --git a/openstackclient/tests/common/test_utils.py b/openstackclient/tests/common/test_utils.py new file mode 100644 index 000000000..0ad4ca9bd --- /dev/null +++ b/openstackclient/tests/common/test_utils.py @@ -0,0 +1,59 @@ +# Copyright 2012-2013 OpenStack, LLC. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +import mock + +from openstackclient.common import exceptions +from openstackclient.common import utils +from openstackclient.tests import utils as test_utils + +PASSWORD = "Pa$$w0rd" +WASSPORD = "Wa$$p0rd" +DROWSSAP = "dr0w$$aP" + + +class TestUtils(test_utils.TestCase): + + def test_get_password_good(self): + with mock.patch("getpass.getpass", return_value=PASSWORD): + mock_stdin = mock.Mock() + mock_stdin.isatty = mock.Mock() + mock_stdin.isatty.return_value = True + self.assertEqual(utils.get_password(mock_stdin), PASSWORD) + + def test_get_password_bad_once(self): + answers = [PASSWORD, WASSPORD, DROWSSAP, DROWSSAP] + with mock.patch("getpass.getpass", side_effect=answers): + mock_stdin = mock.Mock() + mock_stdin.isatty = mock.Mock() + mock_stdin.isatty.return_value = True + self.assertEqual(utils.get_password(mock_stdin), DROWSSAP) + + def test_get_password_no_tty(self): + mock_stdin = mock.Mock() + mock_stdin.isatty = mock.Mock() + mock_stdin.isatty.return_value = False + self.assertRaises(exceptions.CommandError, + utils.get_password, + mock_stdin) + + def test_get_password_cntrl_d(self): + with mock.patch("getpass.getpass", side_effect=EOFError()): + mock_stdin = mock.Mock() + mock_stdin.isatty = mock.Mock() + mock_stdin.isatty.return_value = True + self.assertRaises(exceptions.CommandError, + utils.get_password, + mock_stdin) diff --git a/openstackclient/tests/identity/v2_0/test_user.py b/openstackclient/tests/identity/v2_0/test_user.py index a18d13d82..dfb358ffd 100644 --- a/openstackclient/tests/identity/v2_0/test_user.py +++ b/openstackclient/tests/identity/v2_0/test_user.py @@ -14,6 +14,7 @@ # import copy +import mock from openstackclient.identity.v2_0 import user from openstackclient.tests import fakes @@ -99,6 +100,7 @@ class TestUserCreate(TestUser): ] verifylist = [ ('name', identity_fakes.user_name), + ('password_prompt', False), ('password', 'secret') ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -130,6 +132,47 @@ class TestUserCreate(TestUser): ) self.assertEqual(data, datalist) + def test_user_create_password_prompt(self): + arglist = [ + '--password-prompt', + identity_fakes.user_name, + ] + verifylist = [ + ('name', identity_fakes.user_name), + ('password_prompt', True) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # DisplayCommandBase.take_action() returns two tuples + mocker = mock.Mock() + mocker.return_value = 'abc123' + with mock.patch("openstackclient.common.utils.get_password", mocker): + columns, data = self.cmd.take_action(parsed_args) + + # Set expected values + kwargs = { + 'enabled': True, + 'tenant_id': None, + } + # UserManager.create(name, password, email, tenant_id=, enabled=) + self.users_mock.create.assert_called_with( + identity_fakes.user_name, + 'abc123', + None, + **kwargs + ) + + collist = ('email', 'enabled', 'id', 'name', 'project_id') + self.assertEqual(columns, collist) + datalist = ( + identity_fakes.user_email, + True, + identity_fakes.user_id, + identity_fakes.user_name, + identity_fakes.project_id, + ) + self.assertEqual(data, datalist) + def test_user_create_email(self): arglist = [ '--email', 'barney@example.com', @@ -498,6 +541,7 @@ class TestUserSet(TestUser): verifylist = [ ('name', None), ('password', 'secret'), + ('password_prompt', False), ('email', None), ('project', None), ('enable', False), @@ -515,6 +559,35 @@ class TestUserSet(TestUser): 'secret', ) + def test_user_set_password_prompt(self): + arglist = [ + '--password-prompt', + identity_fakes.user_name, + ] + verifylist = [ + ('name', None), + ('password', None), + ('password_prompt', True), + ('email', None), + ('project', None), + ('enable', False), + ('disable', False), + ('user', identity_fakes.user_name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # DisplayCommandBase.take_action() returns two tuples + mocker = mock.Mock() + mocker.return_value = 'abc123' + with mock.patch("openstackclient.common.utils.get_password", mocker): + self.cmd.take_action(parsed_args) + + # UserManager.update_password(user, password) + self.users_mock.update_password.assert_called_with( + identity_fakes.user_id, + 'abc123', + ) + def test_user_set_email(self): arglist = [ '--email', 'barney@example.com', diff --git a/openstackclient/tests/identity/v3/test_user.py b/openstackclient/tests/identity/v3/test_user.py index 4321b0479..af7b2f70c 100644 --- a/openstackclient/tests/identity/v3/test_user.py +++ b/openstackclient/tests/identity/v3/test_user.py @@ -14,6 +14,7 @@ # import copy +import mock from openstackclient.identity.v3 import user from openstackclient.tests import fakes @@ -118,6 +119,7 @@ class TestUserCreate(TestUser): ] verifylist = [ ('password', 'secret'), + ('password_prompt', False), ('enable', False), ('disable', False), ('name', identity_fakes.user_name), @@ -155,6 +157,54 @@ class TestUserCreate(TestUser): ) self.assertEqual(data, datalist) + def test_user_create_password_prompt(self): + arglist = [ + '--password-prompt', + identity_fakes.user_name, + ] + verifylist = [ + ('password', None), + ('password_prompt', True), + ('enable', False), + ('disable', False), + ('name', identity_fakes.user_name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # DisplayCommandBase.take_action() returns two tuples + mocker = mock.Mock() + mocker.return_value = 'abc123' + with mock.patch("openstackclient.common.utils.get_password", mocker): + columns, data = self.cmd.take_action(parsed_args) + + # Set expected values + kwargs = { + 'default_project': None, + 'description': None, + 'domain': None, + 'email': None, + 'enabled': True, + 'password': 'abc123', + } + # UserManager.create(name, domain=, project=, password=, email=, + # description=, enabled=, default_project=) + self.users_mock.create.assert_called_with( + identity_fakes.user_name, + **kwargs + ) + + collist = ('domain_id', 'email', 'enabled', 'id', 'name', 'project_id') + self.assertEqual(columns, collist) + datalist = ( + identity_fakes.domain_id, + identity_fakes.user_email, + True, + identity_fakes.user_id, + identity_fakes.user_name, + identity_fakes.project_id, + ) + self.assertEqual(data, datalist) + def test_user_create_email(self): arglist = [ '--email', 'barney@example.com', @@ -761,6 +811,7 @@ class TestUserSet(TestUser): verifylist = [ ('name', None), ('password', 'secret'), + ('password_prompt', False), ('email', None), ('domain', None), ('project', None), @@ -785,6 +836,42 @@ class TestUserSet(TestUser): **kwargs ) + def test_user_set_password_prompt(self): + arglist = [ + '--password-prompt', + identity_fakes.user_name, + ] + verifylist = [ + ('name', None), + ('password', None), + ('password_prompt', True), + ('email', None), + ('domain', None), + ('project', None), + ('enable', False), + ('disable', False), + ('user', identity_fakes.user_name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # DisplayCommandBase.take_action() returns two tuples + mocker = mock.Mock() + mocker.return_value = 'abc123' + with mock.patch("openstackclient.common.utils.get_password", mocker): + self.cmd.take_action(parsed_args) + + # Set expected values + kwargs = { + 'enabled': True, + 'password': 'abc123', + } + # UserManager.update(user, name=, domain=, project=, password=, + # email=, description=, enabled=, default_project=) + self.users_mock.update.assert_called_with( + identity_fakes.user_id, + **kwargs + ) + def test_user_set_email(self): arglist = [ '--email', 'barney@example.com',