Skip to content

Commit 14dbfe4

Browse files
author
Dean Troyer
committed
Defer auth prompting until it is actually needed
Auth option prompting happens waaaay to early in the default os-client-config flow, we need to defer it until adter the commands have been parsed. This is why ClientManager.setup_auth() exists, as it is not called until the first attempt to connect to a server occurs. Commands that do not require authentication never hit this. Also, required options were not being enforced. By doing this we handle when no authentication info is present, we fail on missing auth-url rather than attempt to prompt for a password (default auth is password). Closes-Bug: 1619274 Change-Id: Ia4eae350e6904c9eb2c8507d9b3429fe52418726
1 parent 5940439 commit 14dbfe4

File tree

3 files changed

+81
-3
lines changed

3 files changed

+81
-3
lines changed

openstackclient/common/client_config.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
import logging
1717

1818
from os_client_config import config
19+
from os_client_config import exceptions as occ_exceptions
1920

2021

2122
LOG = logging.getLogger(__name__)
@@ -182,6 +183,14 @@ def auth_config_hook(self, config):
182183
LOG.debug("auth_config_hook(): %s" % config)
183184
return config
184185

186+
def load_auth_plugin(self, config):
187+
"""Get auth plugin and validate args"""
188+
189+
loader = self._get_auth_loader(config)
190+
config = self._validate_auth(config, loader)
191+
auth_plugin = loader.load_from_options(**config['auth'])
192+
return auth_plugin
193+
185194
def _validate_auth_ksc(self, config, cloud, fixed_argparse=None):
186195
"""Old compatibility hack for OSC, no longer needed/wanted"""
187196
return config
@@ -192,6 +201,8 @@ def _validate_auth(self, config, loader, fixed_argparse=None):
192201

193202
plugin_options = loader.get_options()
194203

204+
msgs = []
205+
prompt_options = []
195206
for p_opt in plugin_options:
196207
# if it's in config, win, move it and kill it from config dict
197208
# if it's in config.auth but not in config we're good
@@ -202,6 +213,16 @@ def _validate_auth(self, config, loader, fixed_argparse=None):
202213
winning_value = self._find_winning_auth_value(
203214
p_opt, config['auth'])
204215

216+
# if the plugin tells us that this value is required
217+
# then error if it's doesn't exist now
218+
if not winning_value and p_opt.required:
219+
msgs.append(
220+
'Missing value {auth_key}'
221+
' required for auth plugin {plugin}'.format(
222+
auth_key=p_opt.name, plugin=config.get('auth_type'),
223+
)
224+
)
225+
205226
# Clean up after ourselves
206227
for opt in [p_opt.name] + [o.name for o in p_opt.deprecated]:
207228
opt = opt.replace('-', '_')
@@ -224,6 +245,13 @@ def _validate_auth(self, config, loader, fixed_argparse=None):
224245
p_opt.dest not in config['auth'] and
225246
self._pw_callback is not None
226247
):
248+
# Defer these until we know all required opts are present
249+
prompt_options.append(p_opt)
250+
251+
if msgs:
252+
raise occ_exceptions.OpenStackConfigException('\n'.join(msgs))
253+
else:
254+
for p_opt in prompt_options:
227255
config['auth'][p_opt.dest] = self._pw_callback(p_opt.prompt)
228256

229257
return config

openstackclient/common/clientmanager.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,26 @@ def __init__(
6060
self._cacert = self.cacert
6161
self._insecure = not self.verify
6262

63+
def setup_auth(self):
64+
"""Set up authentication"""
65+
66+
if self._auth_setup_completed:
67+
return
68+
69+
# NOTE(dtroyer): Validate the auth args; this is protected with 'if'
70+
# because openstack_config is an optional argument to
71+
# CloudConfig.__init__() and we'll die if it was not
72+
# passed.
73+
if self._cli_options._openstack_config is not None:
74+
self._cli_options._openstack_config._pw_callback = \
75+
shell.prompt_for_password
76+
self._cli_options._auth = \
77+
self._cli_options._openstack_config.load_auth_plugin(
78+
self._cli_options.config,
79+
)
80+
81+
return super(ClientManager, self).setup_auth()
82+
6383
def is_network_endpoint_enabled(self):
6484
"""Check if the network endpoint is enabled"""
6585

openstackclient/shell.py

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -140,12 +140,11 @@ def initialize_app(self, argv):
140140
# First, throw away what has already been done with o-c-c and
141141
# use our own.
142142
try:
143-
cc = cloud_config.OSC_Config(
143+
self.cloud_config = cloud_config.OSC_Config(
144144
override_defaults={
145145
'interface': None,
146146
'auth_type': self._auth_type,
147147
},
148-
pw_func=shell.prompt_for_password,
149148
)
150149
except (IOError, OSError) as e:
151150
self.log.critical("Could not read clouds.yaml configuration file")
@@ -154,9 +153,13 @@ def initialize_app(self, argv):
154153

155154
if not self.options.debug:
156155
self.options.debug = None
157-
self.cloud = cc.get_one_cloud(
156+
157+
# NOTE(dtroyer): Need to do this with validate=False to defer the
158+
# auth plugin handling to ClientManager.setup_auth()
159+
self.cloud = self.cloud_config.get_one_cloud(
158160
cloud=self.options.cloud,
159161
argparse=self.options,
162+
validate=False,
160163
)
161164

162165
# Then, re-create the client_manager with the correct arguments
@@ -165,6 +168,33 @@ def initialize_app(self, argv):
165168
api_version=self.api_version,
166169
)
167170

171+
def prepare_to_run_command(self, cmd):
172+
"""Set up auth and API versions"""
173+
174+
# TODO(dtroyer): Move this to osc-lib
175+
# NOTE(dtroyer): If auth is not required for a command, force fake
176+
# token auth so KSA plugins are happy
177+
178+
kwargs = {}
179+
if not cmd.auth_required:
180+
# Build fake token creds to keep ksa and o-c-c hushed
181+
kwargs['auth_type'] = 'token_endpoint'
182+
kwargs['auth'] = {}
183+
kwargs['auth']['token'] = 'x'
184+
kwargs['auth']['url'] = 'x'
185+
186+
# Validate auth options
187+
self.cloud = self.cloud_config.get_one_cloud(
188+
cloud=self.options.cloud,
189+
argparse=self.options,
190+
validate=True,
191+
**kwargs
192+
)
193+
# Push the updated args into ClientManager
194+
self.client_manager._cli_options = self.cloud
195+
196+
return super(OpenStackShell, self).prepare_to_run_command(cmd)
197+
168198

169199
def main(argv=None):
170200
if argv is None:

0 commit comments

Comments
 (0)