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
This commit is contained in:
parent
2f5e8232aa
commit
033f27fe4d
@ -15,6 +15,7 @@
|
|||||||
|
|
||||||
"""Common client utilities"""
|
"""Common client utilities"""
|
||||||
|
|
||||||
|
import getpass
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import six
|
import six
|
||||||
@ -229,3 +230,18 @@ def get_effective_log_level():
|
|||||||
for handler in root_log.handlers:
|
for handler in root_log.handlers:
|
||||||
min_log_lvl = min(min_log_lvl, handler.level)
|
min_log_lvl = min(min_log_lvl, handler.level)
|
||||||
return min_log_lvl
|
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.")
|
||||||
|
@ -42,6 +42,12 @@ class CreateUser(show.ShowOne):
|
|||||||
metavar='<user-password>',
|
metavar='<user-password>',
|
||||||
help='New user password',
|
help='New user password',
|
||||||
)
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'--password-prompt',
|
||||||
|
dest="password_prompt",
|
||||||
|
action="store_true",
|
||||||
|
help='Prompt interactively for password',
|
||||||
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--email',
|
'--email',
|
||||||
metavar='<user-email>',
|
metavar='<user-email>',
|
||||||
@ -80,6 +86,8 @@ class CreateUser(show.ShowOne):
|
|||||||
enabled = True
|
enabled = True
|
||||||
if parsed_args.disable:
|
if parsed_args.disable:
|
||||||
enabled = False
|
enabled = False
|
||||||
|
if parsed_args.password_prompt:
|
||||||
|
parsed_args.password = utils.get_password(self.app.stdin)
|
||||||
|
|
||||||
user = identity_client.users.create(
|
user = identity_client.users.create(
|
||||||
parsed_args.name,
|
parsed_args.name,
|
||||||
@ -224,6 +232,12 @@ class SetUser(command.Command):
|
|||||||
metavar='<user-password>',
|
metavar='<user-password>',
|
||||||
help='New user password',
|
help='New user password',
|
||||||
)
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'--password-prompt',
|
||||||
|
dest="password_prompt",
|
||||||
|
action="store_true",
|
||||||
|
help='Prompt interactively for password',
|
||||||
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--email',
|
'--email',
|
||||||
metavar='<user-email>',
|
metavar='<user-email>',
|
||||||
@ -251,6 +265,9 @@ class SetUser(command.Command):
|
|||||||
self.log.debug('take_action(%s)' % parsed_args)
|
self.log.debug('take_action(%s)' % parsed_args)
|
||||||
identity_client = self.app.client_manager.identity
|
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
|
if (not parsed_args.name
|
||||||
and not parsed_args.name
|
and not parsed_args.name
|
||||||
and not parsed_args.password
|
and not parsed_args.password
|
||||||
|
@ -43,6 +43,12 @@ class CreateUser(show.ShowOne):
|
|||||||
metavar='<user-password>',
|
metavar='<user-password>',
|
||||||
help='New user password',
|
help='New user password',
|
||||||
)
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'--password-prompt',
|
||||||
|
dest="password_prompt",
|
||||||
|
action="store_true",
|
||||||
|
help='Prompt interactively for password',
|
||||||
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--email',
|
'--email',
|
||||||
metavar='<user-email>',
|
metavar='<user-email>',
|
||||||
@ -97,6 +103,8 @@ class CreateUser(show.ShowOne):
|
|||||||
enabled = True
|
enabled = True
|
||||||
if parsed_args.disable:
|
if parsed_args.disable:
|
||||||
enabled = False
|
enabled = False
|
||||||
|
if parsed_args.password_prompt:
|
||||||
|
parsed_args.password = utils.get_password(self.app.stdin)
|
||||||
|
|
||||||
user = identity_client.users.create(
|
user = identity_client.users.create(
|
||||||
parsed_args.name,
|
parsed_args.name,
|
||||||
@ -273,6 +281,12 @@ class SetUser(command.Command):
|
|||||||
metavar='<user-password>',
|
metavar='<user-password>',
|
||||||
help='New user password',
|
help='New user password',
|
||||||
)
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'--password-prompt',
|
||||||
|
dest="password_prompt",
|
||||||
|
action="store_true",
|
||||||
|
help='Prompt interactively for password',
|
||||||
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--email',
|
'--email',
|
||||||
metavar='<user-email>',
|
metavar='<user-email>',
|
||||||
@ -310,6 +324,9 @@ class SetUser(command.Command):
|
|||||||
self.log.debug('take_action(%s)' % parsed_args)
|
self.log.debug('take_action(%s)' % parsed_args)
|
||||||
identity_client = self.app.client_manager.identity
|
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
|
if (not parsed_args.name
|
||||||
and not parsed_args.name
|
and not parsed_args.name
|
||||||
and not parsed_args.password
|
and not parsed_args.password
|
||||||
|
59
openstackclient/tests/common/test_utils.py
Normal file
59
openstackclient/tests/common/test_utils.py
Normal file
@ -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)
|
@ -14,6 +14,7 @@
|
|||||||
#
|
#
|
||||||
|
|
||||||
import copy
|
import copy
|
||||||
|
import mock
|
||||||
|
|
||||||
from openstackclient.identity.v2_0 import user
|
from openstackclient.identity.v2_0 import user
|
||||||
from openstackclient.tests import fakes
|
from openstackclient.tests import fakes
|
||||||
@ -99,6 +100,7 @@ class TestUserCreate(TestUser):
|
|||||||
]
|
]
|
||||||
verifylist = [
|
verifylist = [
|
||||||
('name', identity_fakes.user_name),
|
('name', identity_fakes.user_name),
|
||||||
|
('password_prompt', False),
|
||||||
('password', 'secret')
|
('password', 'secret')
|
||||||
]
|
]
|
||||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||||
@ -130,6 +132,47 @@ class TestUserCreate(TestUser):
|
|||||||
)
|
)
|
||||||
self.assertEqual(data, datalist)
|
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):
|
def test_user_create_email(self):
|
||||||
arglist = [
|
arglist = [
|
||||||
'--email', 'barney@example.com',
|
'--email', 'barney@example.com',
|
||||||
@ -498,6 +541,7 @@ class TestUserSet(TestUser):
|
|||||||
verifylist = [
|
verifylist = [
|
||||||
('name', None),
|
('name', None),
|
||||||
('password', 'secret'),
|
('password', 'secret'),
|
||||||
|
('password_prompt', False),
|
||||||
('email', None),
|
('email', None),
|
||||||
('project', None),
|
('project', None),
|
||||||
('enable', False),
|
('enable', False),
|
||||||
@ -515,6 +559,35 @@ class TestUserSet(TestUser):
|
|||||||
'secret',
|
'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):
|
def test_user_set_email(self):
|
||||||
arglist = [
|
arglist = [
|
||||||
'--email', 'barney@example.com',
|
'--email', 'barney@example.com',
|
||||||
|
@ -14,6 +14,7 @@
|
|||||||
#
|
#
|
||||||
|
|
||||||
import copy
|
import copy
|
||||||
|
import mock
|
||||||
|
|
||||||
from openstackclient.identity.v3 import user
|
from openstackclient.identity.v3 import user
|
||||||
from openstackclient.tests import fakes
|
from openstackclient.tests import fakes
|
||||||
@ -118,6 +119,7 @@ class TestUserCreate(TestUser):
|
|||||||
]
|
]
|
||||||
verifylist = [
|
verifylist = [
|
||||||
('password', 'secret'),
|
('password', 'secret'),
|
||||||
|
('password_prompt', False),
|
||||||
('enable', False),
|
('enable', False),
|
||||||
('disable', False),
|
('disable', False),
|
||||||
('name', identity_fakes.user_name),
|
('name', identity_fakes.user_name),
|
||||||
@ -155,6 +157,54 @@ class TestUserCreate(TestUser):
|
|||||||
)
|
)
|
||||||
self.assertEqual(data, datalist)
|
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):
|
def test_user_create_email(self):
|
||||||
arglist = [
|
arglist = [
|
||||||
'--email', 'barney@example.com',
|
'--email', 'barney@example.com',
|
||||||
@ -761,6 +811,7 @@ class TestUserSet(TestUser):
|
|||||||
verifylist = [
|
verifylist = [
|
||||||
('name', None),
|
('name', None),
|
||||||
('password', 'secret'),
|
('password', 'secret'),
|
||||||
|
('password_prompt', False),
|
||||||
('email', None),
|
('email', None),
|
||||||
('domain', None),
|
('domain', None),
|
||||||
('project', None),
|
('project', None),
|
||||||
@ -785,6 +836,42 @@ class TestUserSet(TestUser):
|
|||||||
**kwargs
|
**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):
|
def test_user_set_email(self):
|
||||||
arglist = [
|
arglist = [
|
||||||
'--email', 'barney@example.com',
|
'--email', 'barney@example.com',
|
||||||
|
Loading…
x
Reference in New Issue
Block a user