Merge branch 'keyring' into 'master'

Use keyring module to also handle password storage on linux

See merge request gajim/gajim!206
This commit is contained in:
Philipp Hörist 2018-02-06 20:52:44 +01:00
commit 624a2c5cbc
4 changed files with 34 additions and 82 deletions

View file

@ -1,6 +1,6 @@
import subprocess import subprocess
__version__ = "0.98.2" __version__ = "0.98.3"
try: try:
node = subprocess.Popen('git rev-parse --short=12 HEAD', shell=True, node = subprocess.Popen('git rev-parse --short=12 HEAD', shell=True,

View file

@ -249,6 +249,8 @@ class OptionsParser:
self.update_config_to_016112() self.update_config_to_016112()
if old < [0, 98, 2] and new >= [0, 98, 2]: if old < [0, 98, 2] and new >= [0, 98, 2]:
self.update_config_to_0982() self.update_config_to_0982()
if old < [0, 98, 3] and new >= [0, 98, 3]:
self.update_config_to_0983()
app.logger.init_vars() app.logger.init_vars()
app.logger.attach_cache_database() app.logger.attach_cache_database()
@ -924,3 +926,12 @@ class OptionsParser:
''' '''
) )
app.config.set('version', '0.98.2') app.config.set('version', '0.98.2')
def update_config_to_0983(self):
for account in self.old_values['accounts'].keys():
password = self.old_values['accounts'][account]['password']
if password == "winvault:":
app.config.set_per('accounts', account, 'password', 'keyring:')
elif password == "libsecret:":
app.config.set_per('accounts', account, 'password', '')
app.config.set('version', '0.98.3')

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,7 +33,6 @@ __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:
@ -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,17 @@ 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.secret = None
self.winsecret = None
self.connect_backends() self.connect_backends()
self.set_preferred_backend() self.set_preferred_backend()
@ -131,25 +86,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 +102,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 +121,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"]
}
) )