diff --git a/osc/conf.py b/osc/conf.py index 07c6c967..5770c6b4 100644 --- a/osc/conf.py +++ b/osc/conf.py @@ -764,7 +764,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: @@ -884,6 +884,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 @@ -979,7 +981,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) diff --git a/osc/credentials.py b/osc/credentials.py index 8dceb3f6..a14fb60e 100644 --- a/osc/credentials.py +++ b/osc/credentials.py @@ -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