Use keyring module to handle password storage

This commit is contained in:
André Apitzsch 2018-02-03 20:32:37 +01:00
parent d8020f18a1
commit d0f4ebd0bc
2 changed files with 22 additions and 80 deletions

View File

@ -24,11 +24,7 @@
## along with Gajim. If not, see <http://www.gnu.org/licenses/>. ## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
## ##
import os
import logging import logging
import gi
from gi.repository import GLib
from gajim.common import app from gajim.common import app
@ -37,11 +33,10 @@ __all__ = ['get_password', 'save_password']
log = logging.getLogger('gajim.password') log = logging.getLogger('gajim.password')
keyring = None keyring = None
if os.name == 'nt': try:
try: import keyring
import keyring except ImportError:
except ImportError: log.debug('python-keyring missing, falling back to plaintext storage')
log.debug('python-keyring missing, falling back to plaintext storage')
class PasswordStorage(object): class PasswordStorage(object):
@ -54,54 +49,16 @@ class PasswordStorage(object):
raise NotImplementedError raise NotImplementedError
class LibSecretPasswordStorage(PasswordStorage): class SecretPasswordStorage(PasswordStorage):
"""Store password using libsecret""" """ Store password using Keyring """
identifier = 'libsecret:' identifier = 'keyring:'
def __init__(self):
gi.require_version('Secret', '1')
gir = __import__('gi.repository', globals(), locals(), ['Secret'], 0)
self.Secret = gir.Secret
self.GAJIM_SCHEMA = self.Secret.Schema.new(
"org.gnome.keyring.NetworkPassword",
self.Secret.SchemaFlags.NONE,
{
'user': self.Secret.SchemaAttributeType.STRING,
'server': self.Secret.SchemaAttributeType.STRING,
'protocol': self.Secret.SchemaAttributeType.STRING,
}
)
def get_password(self, account_name):
server = app.config.get_per('accounts', account_name, 'hostname')
user = app.config.get_per('accounts', account_name, 'name')
password = self.Secret.password_lookup_sync(self.GAJIM_SCHEMA,
{'user': user, 'server': server, 'protocol': 'xmpp'}, None)
return password
def save_password(self, account_name, password, update=True):
server = app.config.get_per('accounts', account_name, 'hostname')
user = app.config.get_per('accounts', account_name, 'name')
display_name = _('XMPP account %s') % user + '@' + server
attributes = {'user': user, 'server': server, 'protocol': 'xmpp'}
try:
return self.Secret.password_store_sync(
self.GAJIM_SCHEMA, attributes, self.Secret.COLLECTION_DEFAULT,
display_name, password or '', None)
except GLib.Error as error:
log.error(error)
return False
class SecretWindowsPasswordStorage(PasswordStorage):
""" Windows Keyring """
identifier = 'winvault:'
def __init__(self): def __init__(self):
self.win_keyring = keyring.get_keyring() self.keyring = keyring.get_keyring()
def save_password(self, account_name, password): def save_password(self, account_name, password):
try: try:
self.win_keyring.set_password('gajim', account_name, password) self.keyring.set_password('gajim', account_name, password)
return True return True
except: except:
log.exception('error:') log.exception('error:')
@ -109,19 +66,18 @@ class SecretWindowsPasswordStorage(PasswordStorage):
def get_password(self, account_name): def get_password(self, account_name):
log.debug('getting password') log.debug('getting password')
return self.win_keyring.get_password('gajim', account_name) return self.keyring.get_password('gajim', account_name)
class PasswordStorageManager(PasswordStorage): class PasswordStorageManager(PasswordStorage):
"""Access all the implemented password storage backends, knowing which ones """Access all the implemented password storage backends, knowing which ones
are available and which we prefer to use. are available and which we prefer to use.
Also implements storing directly in gajim config (former Also implements storing directly in gajim config."""
SimplePasswordStorage class)."""
def __init__(self): def __init__(self):
self.preferred_backend = None self.preferred_backend = None
self.libsecret = None self.libsecret = None
self.winsecret = None self.secret = None
self.connect_backends() self.connect_backends()
self.set_preferred_backend() self.set_preferred_backend()
@ -131,25 +87,15 @@ class PasswordStorageManager(PasswordStorage):
""" """
# TODO: handle disappearing backends # TODO: handle disappearing backends
if app.config.get('use_keyring'): if app.config.get('use_keyring') and keyring:
if os.name == 'nt' and keyring: self.secret = SecretPasswordStorage()
self.winsecret = SecretWindowsPasswordStorage()
else:
try:
self.libsecret = LibSecretPasswordStorage()
except (ValueError, AttributeError) as e:
log.debug("Could not connect to libsecret: %s" % e)
def get_password(self, account_name): def get_password(self, account_name):
pw = app.config.get_per('accounts', account_name, 'password') pw = app.config.get_per('accounts', account_name, 'password')
if not pw: if not pw:
return pw return pw
if pw.startswith(LibSecretPasswordStorage.identifier) and \ if pw.startswith(SecretPasswordStorage.identifier) and self.secret:
self.libsecret: backend = self.secret
backend = self.libsecret
elif pw.startswith(SecretWindowsPasswordStorage.identifier) and \
self.winsecret:
backend = self.winsecret
else: else:
backend = None backend = None
@ -157,7 +103,7 @@ class PasswordStorageManager(PasswordStorage):
pw = backend.get_password(account_name) pw = backend.get_password(account_name)
if backend != self.preferred_backend: if backend != self.preferred_backend:
# migrate password to preferred_backend # migrate password to preferred_backend
self.preferred_backend.save_password(account_name, pw) self.save_password(account_name, pw)
# TODO: remove from old backend # TODO: remove from old backend
return pw return pw
@ -176,18 +122,11 @@ class PasswordStorageManager(PasswordStorage):
return True return True
def set_preferred_backend(self): def set_preferred_backend(self):
if self.libsecret: if self.secret:
self.preferred_backend = self.libsecret self.preferred_backend = self.secret
elif self.winsecret:
self.preferred_backend = self.winsecret
else: else:
self.preferred_backend = None self.preferred_backend = None
def has_keyring(self):
"""Is there a real password storage backend? Else, passwords are stored
plain in gajim config"""
return bool(self.preferred_backend)
passwordStorageManager = None passwordStorageManager = None
def get_storage(): def get_storage():

View File

@ -280,4 +280,7 @@ setup(
'pyOpenSSL>=0.12', 'pyOpenSSL>=0.12',
'pyasn1', 'pyasn1',
], ],
extras_require={
'secret_password': ["keyring"]
}
) )