Clean up shell authentication
* Remove the auth option checks as the auth plugins will validate their own options * Move the initialization of client_manager to the end of initialize_app() so it is always called. Note that no attempts to actually authenticate occur until the first use of one of the client attributes in client_manager. This leaves initialize_clientmanager() (formerly uathenticate_user()) empty so remove it. * Remove interact() as the client_manager has already been created And there is nothing left. * prepare_to_run_command() is reduced to trigger an authentication attempt for the best_effort auth commands, currently the only one is 'complete'. * Add prompt_for_password() to ask the user to enter a password when necessary. Passed to ClientManager in a new kward pw_func. Bug: 1355838 Change-Id: I9fdec9144c4c84f65aed1cf91ce41fe1895089b2
This commit is contained in:
parent
08ada52d6f
commit
f766bca5f9
@ -62,7 +62,7 @@ def select_auth_plugin(options):
|
||||
if options.os_url and options.os_token:
|
||||
# service token authentication
|
||||
auth_plugin = 'token_endpoint'
|
||||
elif options.os_password:
|
||||
elif options.os_username:
|
||||
if options.os_identity_api_version == '3':
|
||||
auth_plugin = 'v3password'
|
||||
elif options.os_identity_api_version == '2.0':
|
||||
|
@ -55,17 +55,46 @@ class ClientManager(object):
|
||||
for o in auth.OPTIONS_LIST]:
|
||||
return self._auth_params[name[1:]]
|
||||
|
||||
def __init__(self, auth_options, api_version=None, verify=True):
|
||||
def __init__(
|
||||
self,
|
||||
auth_options,
|
||||
api_version=None,
|
||||
verify=True,
|
||||
pw_func=None,
|
||||
):
|
||||
"""Set up a ClientManager
|
||||
|
||||
:param auth_options:
|
||||
Options collected from the command-line, environment, or wherever
|
||||
:param api_version:
|
||||
Dict of API versions: key is API name, value is the version
|
||||
:param verify:
|
||||
TLS certificate verification; may be a boolean to enable or disable
|
||||
server certificate verification, or a filename of a CA certificate
|
||||
bundle to be used in verification (implies True)
|
||||
:param pw_func:
|
||||
Callback function for asking the user for a password. The function
|
||||
takes an optional string for the prompt ('Password: ' on None) and
|
||||
returns a string containig the password
|
||||
"""
|
||||
|
||||
# If no plugin is named by the user, select one based on
|
||||
# the supplied options
|
||||
if not auth_options.os_auth_plugin:
|
||||
auth_options.os_auth_plugin = auth.select_auth_plugin(auth_options)
|
||||
|
||||
self._auth_plugin = auth_options.os_auth_plugin
|
||||
|
||||
# Horrible hack alert...must handle prompt for null password if
|
||||
# password auth is requested.
|
||||
if (self._auth_plugin.endswith('password') and
|
||||
not auth_options.os_password):
|
||||
auth_options.os_password = pw_func()
|
||||
|
||||
self._url = auth_options.os_url
|
||||
self._auth_params = auth.build_auth_params(auth_options)
|
||||
self._region_name = auth_options.os_region_name
|
||||
self._api_version = api_version
|
||||
self._auth_ref = None
|
||||
self.timing = auth_options.timing
|
||||
|
||||
# For compatibility until all clients can be updated
|
||||
@ -99,13 +128,16 @@ class ClientManager(object):
|
||||
verify=verify,
|
||||
)
|
||||
|
||||
self.auth_ref = None
|
||||
if 'token' not in self._auth_params:
|
||||
LOG.debug("Get service catalog")
|
||||
self.auth_ref = self.auth.get_auth_ref(self.session)
|
||||
|
||||
return
|
||||
|
||||
@property
|
||||
def auth_ref(self):
|
||||
"""Dereference will trigger an auth if it hasn't already"""
|
||||
if not self._auth_ref:
|
||||
LOG.debug("Get auth_ref")
|
||||
self._auth_ref = self.auth.get_auth_ref(self.session)
|
||||
return self._auth_ref
|
||||
|
||||
def get_endpoint_for_service_type(self, service_type, region_name=None):
|
||||
"""Return the endpoint URL for the service type."""
|
||||
# See if we are using password flow auth, i.e. we have a
|
||||
|
@ -36,6 +36,30 @@ from openstackclient.common import utils
|
||||
DEFAULT_DOMAIN = 'default'
|
||||
|
||||
|
||||
def prompt_for_password(prompt=None):
|
||||
"""Prompt user for a password
|
||||
|
||||
Propmpt for a password if stdin is a tty.
|
||||
"""
|
||||
|
||||
if not prompt:
|
||||
prompt = 'Password: '
|
||||
pw = 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:
|
||||
pw = getpass.getpass(prompt)
|
||||
except EOFError:
|
||||
pass
|
||||
# No password because we did't have a tty or nothing was entered
|
||||
if not pw:
|
||||
raise exc.CommandError(
|
||||
"No password entered, or found via --os-password or OS_PASSWORD",
|
||||
)
|
||||
return pw
|
||||
|
||||
|
||||
class OpenStackShell(app.App):
|
||||
|
||||
CONSOLE_MESSAGE_FORMAT = '%(levelname)s: %(name)s %(message)s'
|
||||
@ -206,112 +230,6 @@ class OpenStackShell(app.App):
|
||||
|
||||
return clientmanager.build_plugin_option_parser(parser)
|
||||
|
||||
def initialize_clientmanager(self):
|
||||
"""Validating authentication options and generate a clientmanager"""
|
||||
|
||||
if self.client_manager:
|
||||
self.log.debug('The clientmanager has been initialized already')
|
||||
return
|
||||
|
||||
self.log.debug("validating authentication options")
|
||||
|
||||
# Assuming all auth plugins will be named in the same fashion,
|
||||
# ie vXpluginName
|
||||
if (not self.options.os_url and
|
||||
self.options.os_auth_plugin.startswith('v') and
|
||||
self.options.os_auth_plugin[1] !=
|
||||
self.options.os_identity_api_version[0]):
|
||||
raise exc.CommandError(
|
||||
"Auth plugin %s not compatible"
|
||||
" with requested API version" % self.options.os_auth_plugin
|
||||
)
|
||||
# TODO(mhu) All these checks should be exposed at the plugin level
|
||||
# or just dropped altogether, as the client instantiation will fail
|
||||
# anyway
|
||||
if self.options.os_url and not self.options.os_token:
|
||||
# service token needed
|
||||
raise exc.CommandError(
|
||||
"You must provide a service token via"
|
||||
" either --os-token or env[OS_TOKEN]")
|
||||
|
||||
if (self.options.os_auth_plugin.endswith('token') and
|
||||
(self.options.os_token or self.options.os_auth_url)):
|
||||
# Token flow auth takes priority
|
||||
if not self.options.os_token:
|
||||
raise exc.CommandError(
|
||||
"You must provide a token via"
|
||||
" either --os-token or env[OS_TOKEN]")
|
||||
|
||||
if not self.options.os_auth_url:
|
||||
raise exc.CommandError(
|
||||
"You must provide a service URL via"
|
||||
" either --os-auth-url or env[OS_AUTH_URL]")
|
||||
|
||||
if (not self.options.os_url and
|
||||
not self.options.os_auth_plugin.endswith('token')):
|
||||
# Validate password flow auth
|
||||
if not self.options.os_username:
|
||||
raise exc.CommandError(
|
||||
"You must provide a username via"
|
||||
" either --os-username or env[OS_USERNAME]")
|
||||
|
||||
if not self.options.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:
|
||||
self.options.os_password = getpass.getpass()
|
||||
except EOFError:
|
||||
pass
|
||||
# No password because we did't have a tty or the
|
||||
# user Ctl-D when prompted?
|
||||
if not self.options.os_password:
|
||||
raise exc.CommandError(
|
||||
"You must provide a password via"
|
||||
" either --os-password, or env[OS_PASSWORD], "
|
||||
" or prompted response")
|
||||
|
||||
if not ((self.options.os_project_id
|
||||
or self.options.os_project_name) or
|
||||
(self.options.os_domain_id
|
||||
or self.options.os_domain_name) or
|
||||
self.options.os_trust_id):
|
||||
if self.options.os_auth_plugin.endswith('password'):
|
||||
raise exc.CommandError(
|
||||
"You must provide authentication scope as a project "
|
||||
"or a domain via --os-project-id "
|
||||
"or env[OS_PROJECT_ID], "
|
||||
"--os-project-name or env[OS_PROJECT_NAME], "
|
||||
"--os-domain-id or env[OS_DOMAIN_ID], or"
|
||||
"--os-domain-name or env[OS_DOMAIN_NAME], or "
|
||||
"--os-trust-id or env[OS_TRUST_ID].")
|
||||
|
||||
if not self.options.os_auth_url:
|
||||
raise exc.CommandError(
|
||||
"You must provide an auth url via"
|
||||
" either --os-auth-url or via env[OS_AUTH_URL]")
|
||||
|
||||
if (self.options.os_trust_id and
|
||||
self.options.os_identity_api_version != '3'):
|
||||
raise exc.CommandError(
|
||||
"Trusts can only be used with Identity API v3")
|
||||
|
||||
if (self.options.os_trust_id and
|
||||
((self.options.os_project_id
|
||||
or self.options.os_project_name) or
|
||||
(self.options.os_domain_id
|
||||
or self.options.os_domain_name))):
|
||||
raise exc.CommandError(
|
||||
"Authentication cannot be scoped to multiple targets. "
|
||||
"Pick one of project, domain or trust.")
|
||||
|
||||
self.client_manager = clientmanager.ClientManager(
|
||||
auth_options=self.options,
|
||||
verify=self.verify,
|
||||
api_version=self.api_version,
|
||||
)
|
||||
return
|
||||
|
||||
def initialize_app(self, argv):
|
||||
"""Global app init bits:
|
||||
|
||||
@ -368,19 +286,23 @@ class OpenStackShell(app.App):
|
||||
else:
|
||||
self.verify = not self.options.insecure
|
||||
|
||||
self.client_manager = clientmanager.ClientManager(
|
||||
auth_options=self.options,
|
||||
verify=self.verify,
|
||||
api_version=self.api_version,
|
||||
pw_func=prompt_for_password,
|
||||
)
|
||||
|
||||
def prepare_to_run_command(self, cmd):
|
||||
"""Set up auth and API versions"""
|
||||
self.log.debug('prepare_to_run_command %s', cmd.__class__.__name__)
|
||||
|
||||
if not cmd.auth_required:
|
||||
return
|
||||
if cmd.best_effort:
|
||||
if cmd.auth_required and cmd.best_effort:
|
||||
try:
|
||||
self.initialize_clientmanager()
|
||||
# Trigger the Identity client to initialize
|
||||
self.client_manager.auth_ref
|
||||
except Exception:
|
||||
pass
|
||||
else:
|
||||
self.initialize_clientmanager()
|
||||
return
|
||||
|
||||
def clean_up(self, cmd, result, err):
|
||||
@ -412,12 +334,6 @@ class OpenStackShell(app.App):
|
||||
targs = tparser.parse_args(['-f', format])
|
||||
tcmd.run(targs)
|
||||
|
||||
def interact(self):
|
||||
# NOTE(dtroyer): Maintain the old behaviour for interactive use as
|
||||
# this path does not call prepare_to_run_command()
|
||||
self.initialize_clientmanager()
|
||||
super(OpenStackShell, self).interact()
|
||||
|
||||
|
||||
def main(argv=sys.argv[1:]):
|
||||
return OpenStackShell().run(argv)
|
||||
|
Loading…
x
Reference in New Issue
Block a user