1
0
mirror of https://github.com/openSUSE/osc.git synced 2024-09-20 01:06:17 +02:00

Improve password handling

* Adapt do_config to use the new credentials manager implementation
  and add a --change-password option which can be used to change the
  password.
* Adapt config_set_option to follow the change in do_config.
* Split selection of the credentials manager descriptor to reuse it
  in do_config and interactive_config_setup.
* Introduce new ConfigMissingCredentialsError which is raised in case
  of missing credentials (user or password). In this case the user will
  be asked to enter the new credentials.
This commit is contained in:
lethliel 2019-08-29 11:35:34 +02:00
parent 36ec0c48d4
commit cba4b58bbe
3 changed files with 82 additions and 23 deletions

View File

@ -158,6 +158,12 @@ class Osc(cmdln.Cmdln):
conf.interactive_config_setup(e.file, e.url, initial=False)
if try_again:
self.postoptparse(try_again = False)
except oscerr.ConfigMissingCredentialsError as e:
print(e.msg)
print('Please enter new credentials.')
conf.interactive_config_setup(e.file, e.url, initial=False)
if try_again:
self.postoptparse(try_again = False)
self.options.verbose = conf.config['verbose']
self.download_progress = None
@ -8964,6 +8970,10 @@ Please submit there instead, or use --nodevelproject to force direct submission.
help='indicates that the config value should be read from stdin')
@cmdln.option('-p', '--prompt', action='store_true',
help='prompt for a value')
@cmdln.option('--change-password', action='store_true',
help='Change password')
@cmdln.option('--select-password-store', action='store_true',
help='Change the password store')
@cmdln.option('--no-echo', action='store_true',
help='prompt for a value but do not echo entered characters')
@cmdln.option('--dump', action='store_true',
@ -8983,6 +8993,15 @@ Please submit there instead, or use --nodevelproject to force direct submission.
${cmd_usage}
${cmd_option_list}
"""
prompt_value = 'Value: '
if opts.change_password:
opts.no_echo = True
opts.prompt = True
opts.select_password_store = True
prompt_value = 'Password: '
if len(args) != 1:
raise oscerr.WrongArgs('--change-password only needs the apiurl')
args = [args[0], 'pass']
if len(args) < 2 and not (opts.dump or opts.dump_full):
raise oscerr.WrongArgs('Too few arguments')
elif opts.dump or opts.dump_full:
@ -9016,24 +9035,27 @@ Please submit there instead, or use --nodevelproject to force direct submission.
elif opts.no_echo or opts.prompt:
if opts.no_echo:
import getpass
inp = getpass.getpass('Value: ').strip()
inp = getpass.getpass(prompt_value).strip()
else:
inp = raw_input('Value: ').strip()
inp = raw_input(prompt_value).strip()
if not inp:
raise oscerr.WrongArgs('error: no value was entered')
val = [inp]
opt, newval = conf.config_set_option(section, opt, ' '.join(val), delete=opts.delete, update=True)
creds_mgr_descr = None
if opt == 'pass' and opts.select_password_store:
creds_mgr_descr = conf.select_credentials_manager_descr()
orig_opt = opt
opt, newval = conf.config_set_option(section, opt, ' '.join(val), delete=opts.delete, update=True, creds_mgr_descr=creds_mgr_descr)
if newval is None and opts.delete:
print('\'%s\': \'%s\' got removed' % (section, opt))
elif newval is None:
print('\'%s\': \'%s\' is not set' % (section, opt))
else:
if opts.no_echo:
if orig_opt == 'pass':
print('Password has been changed.')
elif opts.no_echo:
# supress value
print('\'%s\': set \'%s\'' % (section, opt))
elif opt == 'pass' and not conf.config['plaintext_passwd'] and newval == 'your_password':
opt, newval = conf.config_set_option(section, 'passx')
print('\'%s\': \'pass\' was rewritten to \'passx\': \'%s\'' % (section, newval))
else:
print('\'%s\': \'%s\' is set to \'%s\'' % (section, opt, newval))

View File

@ -657,7 +657,7 @@ def write_config(fname, cp):
raise
def config_set_option(section, opt, val=None, delete=False, update=True, **kwargs):
def config_set_option(section, opt, val=None, delete=False, update=True, creds_mgr_descr=None, **kwargs):
"""
Sets a config option. If val is not specified the current/default value is
returned. If val is specified, opt is set to val and the new value is returned.
@ -693,11 +693,41 @@ def config_set_option(section, opt, val=None, delete=False, update=True, **kwarg
raise oscerr.ConfigError('unknown config option \'%s\'' % opt, config['conffile'])
run = False
if val:
cp.set(section, opt, val)
write_config(config['conffile'], cp)
if opt == 'pass':
user = cp.get(section, 'user')
creds_mgr = _get_credentials_manager(section, cp)
if user is None and hasattr(creds_mgr, 'get_user'):
user = creds_mgr.get_user(section)
old_pw = creds_mgr.get_password(section, user, defer=False)
try:
creds_mgr.delete_password(section, user)
if creds_mgr_descr:
creds_mgr_new = creds_mgr_descr.create(cp)
else:
creds_mgr_new = creds_mgr
creds_mgr_new.set_password(section, user, val)
write_config(config['conffile'], cp)
opt = credentials.AbstractCredentialsManager.config_entry
old_pw = None
finally:
if old_pw is not None:
creds_mgr.set_password(section, user, old_pw)
# not nice, but needed if the Credentials Manager will change
# something in cp
write_config(config['conffile'], cp)
else:
cp.set(section, opt, val)
write_config(config['conffile'], cp)
run = True
elif delete and cp.has_option(section, opt):
cp.remove_option(section, opt)
elif delete and (cp.has_option(section, opt) or opt == 'pass'):
if opt == 'pass':
user = cp.get(section, 'user')
creds_mgr = _get_credentials_manager(section, cp)
if user is None and hasattr(creds_mgr, 'get_user'):
user = creds_mgr.get_user(section)
creds_mgr.delete_password(section, user)
else:
cp.remove_option(section, opt)
write_config(config['conffile'], cp)
run = True
if run and update:
@ -857,10 +887,10 @@ def get_config(override_conffile=None,
if user is None and hasattr(creds_mgr, 'get_user'):
user = creds_mgr.get_user(url)
if user is None:
raise oscerr.ConfigError('No user found in section %s' % url, conffile)
raise oscerr.ConfigMissingCredentialsError('No user found in section %s' % url, conffile, url)
password = creds_mgr.get_password(url, user)
if password is None:
raise oscerr.ConfigError('No password found in section %s' % url, conffile)
raise oscerr.ConfigMissingCredentialsError('No password found in section %s' % url, conffile, url)
if cp.has_option(url, 'http_headers'):
http_headers = cp.get(url, 'http_headers')
@ -972,6 +1002,16 @@ def identify_conf():
def interactive_config_setup(conffile, apiurl, initial=True):
user = raw_input('Username: ')
passwd = getpass.getpass()
creds_mgr_descr = select_credentials_manager_descr()
if initial:
config = {'user': user, 'pass': passwd}
if apiurl:
config['apiurl'] = apiurl
write_initial_config(conffile, config, creds_mgr_descriptor=creds_mgr_descr)
else:
add_section(conffile, apiurl, user, passwd, creds_mgr_descriptor=creds_mgr_descr)
def select_credentials_manager_descr():
if not credentials.has_keyring_support():
print('To use keyrings please install python-keyring.')
creds_mgr_descriptors = credentials.get_credentials_manager_descriptors()
@ -983,14 +1023,6 @@ def interactive_config_setup(conffile, apiurl, initial=True):
i = int(i) - 1
if i < 0 or i >= len(creds_mgr_descriptors):
sys.exit('Invalid selection')
creds_mgr_descr = creds_mgr_descriptors[i]
if initial:
config = {'user': user, 'pass': passwd}
if apiurl:
config['apiurl'] = apiurl
write_initial_config(conffile, config, creds_mgr_descriptor=creds_mgr_descr)
else:
add_section(conffile, apiurl, user, passwd, creds_mgr_descriptor=creds_mgr_descr)
return creds_mgr_descriptors[i]
# vim: sw=4 et

View File

@ -28,6 +28,11 @@ class ConfigMissingApiurl(ConfigError):
ConfigError.__init__(self, msg, fname)
self.url = url
class ConfigMissingCredentialsError(ConfigError):
def __init__(self, msg, fname, url):
ConfigError.__init__(self, msg, fname)
self.url = url
class APIError(OscBaseError):
"""Exception raised when there is an error in the output from the API"""
def __init__(self, msg):