1
0
mirror of https://github.com/openSUSE/osc.git synced 2025-01-24 22:06:14 +01:00

Merge commit 'refs/pull/1022/head' of github.com:openSUSE/osc

Only ask for a password if it is really needed for authentication.
The new lazy password approach is much smarter than the old callable
hack. That's why we deprecate returning a callable from
AbstractCredentialsManager.get_password. The current compatibility code
for a callable will be removed in the near future.

Minor nitpick: actually it would have been "cleaner" to introduce a new
subclass like an AbstractLazyPasswordCredentialsManager that encapsulates
the lazy password behavior. Currently, if, for instance, a credentials
manager is always non-lazy it would just override get_password but still
inherits the abstract (and unused) _get_password method.
This commit is contained in:
Marcus Huewe 2022-04-11 15:21:19 +02:00
commit 90ccc84f95
2 changed files with 42 additions and 19 deletions

View File

@ -765,7 +765,7 @@ def config_set_option(section, opt, val=None, delete=False, update=True, creds_m
# change password store
creds_mgr = _get_credentials_manager(section, cp)
user = _extract_user_compat(cp, section, creds_mgr)
val = creds_mgr.get_password(section, user)
val = creds_mgr.get_password(section, user, defer=False)
run = False
if val:
@ -885,6 +885,8 @@ class APIHostOptionsEntry(dict):
def __getitem__(self, key, *args, **kwargs):
value = super(self.__class__, self).__getitem__(key, *args, **kwargs)
if key == 'pass' and callable(value):
print('Warning: use of a deprecated credentials manager API.',
file=sys.stderr)
value = value()
return value
@ -980,7 +982,7 @@ def get_config(override_conffile=None,
user = _extract_user_compat(cp, url, creds_mgr)
if user is None:
raise oscerr.ConfigMissingCredentialsError('No user found in section %s' % url, conffile, url)
password = creds_mgr.get_password(url, user)
password = creds_mgr.get_password(url, user, defer=True)
if password is None:
raise oscerr.ConfigMissingCredentialsError('No password found in section %s' % url, conffile, url)

View File

@ -33,6 +33,31 @@ from . import conf
from . import oscerr
class _LazyPassword(object):
def __init__(self, pwfunc):
self._pwfunc = pwfunc
self._password = None
def __str__(self):
if self._password is None:
self._password = self._pwfunc()
if self._password is None:
raise oscerr.OscIOError(None, 'Unable to retrieve password')
return self._password
def __len__(self):
return len(str(self))
def __add__(self, other):
return str(self) + other
def __radd__(self, other):
return other + str(self)
def __getattr__(self, name):
return getattr(str(self), name)
class AbstractCredentialsManagerDescriptor(object):
def name(self):
raise NotImplementedError()
@ -64,14 +89,15 @@ class AbstractCredentialsManager(object):
def create(cls, cp, options):
return cls(cp, options)
def get_password(self, url, user, defer=True):
# If defer is True a callable can be returned
# and the password is retrieved if the callable
# is called. Implementations are free to ignore
# defer parameter and can directly return the password.
# If defer is False the password is directly returned.
def _get_password(self, url, user):
raise NotImplementedError()
def get_password(self, url, user, defer=True):
if defer:
return _LazyPassword(lambda: self._get_password(url, user))
else:
return self._get_password(url, user)
def set_password(self, url, user, password):
raise NotImplementedError()
@ -162,10 +188,10 @@ class TransientCredentialsManager(AbstractCredentialsManager):
if options is not None:
raise RuntimeError('options must be None')
def get_password(self, url, user, defer=True):
if defer:
return self
return self()
def _get_password(self, url, user):
if self._password is None:
self._password = getpass.getpass('Password: ')
return self._password
def set_password(self, url, user, password):
self._password = password
@ -174,11 +200,6 @@ class TransientCredentialsManager(AbstractCredentialsManager):
def delete_password(self, url, user):
self._password = None
def __call__(self):
if self._password is None:
self._password = getpass.getpass('Password: ')
return self._password
class TransientDescriptor(AbstractCredentialsManagerDescriptor):
def name(self):
@ -214,7 +235,7 @@ class KeyringCredentialsManager(AbstractCredentialsManager):
return None
return super(cls, cls).create(cp, options)
def get_password(self, url, user, defer=True):
def _get_password(self, url, user):
self._load_backend()
return keyring.get_password(urlsplit(url)[1], user)
@ -265,7 +286,7 @@ class GnomeKeyringCredentialsManager(AbstractCredentialsManager):
return None
return super(cls, cls).create(cp, options)
def get_password(self, url, user, defer=True):
def _get_password(self, url, user):
gk_data = self._keyring_data(url, user)
if gk_data is None:
return None