Files
mailnag/Mailnag/common/credentialstore.py
Patrick Ulbrich 3a9bd9f34b Use proper enums
2019-11-04 20:11:38 +01:00

169 lines
4.3 KiB
Python

# Copyright 2015 - 2019 Patrick Ulbrich <zulu99@gmx.net>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
# MA 02110-1301, USA.
#
import hashlib
from enum import Enum
class CredentialStoreType(Enum):
NONE = 'none'
GNOME = 'gnome'
# KDE = 'kde'
_credentialstoretype = CredentialStoreType.NONE
try:
import gi
gi.require_version('GnomeKeyring', '1.0')
from gi.repository import GnomeKeyring
_credentialstoretype = CredentialStoreType.GNOME
except: pass
#
# CredentialStore base class
#
class CredentialStore:
_instance = None
def set(self, key, secret):
pass
def get(self, key):
pass
def remove(self, key):
pass
@staticmethod
def get_default():
if (CredentialStore._instance == None) and (_credentialstoretype != CredentialStoreType.NONE):
CredentialStore._instance = SupportedCredentialStores[_credentialstoretype]()
return CredentialStore._instance
@staticmethod
def from_string(strn):
cs = None
if strn == 'auto':
cs = CredentialStore.get_default()
elif strn in SupportedCredentialStores:
cs = SupportedCredentialStores[strn]()
return cs
#
# GNOME CredentialStore
#
class GnomeCredentialStore(CredentialStore):
def __init__(self):
(result, kr_name) = GnomeKeyring.get_default_keyring_sync()
self._defaultKeyring = kr_name
if self._defaultKeyring == None:
self._defaultKeyring = 'login'
result = GnomeKeyring.unlock_sync(self._defaultKeyring, None)
if result != GnomeKeyring.Result.OK:
raise KeyringUnlockException('Failed to unlock default keyring')
self._migrate_keyring()
def get(self, key):
attrs = self._get_attrs(key)
result, items = GnomeKeyring.find_items_sync(GnomeKeyring.ItemType.GENERIC_SECRET, attrs)
if result == GnomeKeyring.Result.OK:
return items[0].secret
else:
return ''
def set(self, key, secret):
if secret == '':
return
attrs = self._get_attrs(key)
existing_secret = ''
result, items = GnomeKeyring.find_items_sync(GnomeKeyring.ItemType.GENERIC_SECRET, attrs)
if result == GnomeKeyring.Result.OK:
existing_secret = items[0].secret
if existing_secret != secret:
GnomeKeyring.item_create_sync(self._defaultKeyring, \
GnomeKeyring.ItemType.GENERIC_SECRET, key, \
attrs, secret, True)
def remove(self, key):
attrs = self._get_attrs(key)
result, items = GnomeKeyring.find_items_sync(GnomeKeyring.ItemType.GENERIC_SECRET, attrs)
if result == GnomeKeyring.Result.OK:
GnomeKeyring.item_delete_sync(self._defaultKeyring, items[0].item_id)
def _get_attrs(self, key):
attrs = GnomeKeyring.Attribute.list_new()
keyid = hashlib.md5(key.encode('utf-8')).hexdigest()
GnomeKeyring.Attribute.list_append_string(attrs, 'source', 'Mailnag')
GnomeKeyring.Attribute.list_append_string(attrs, 'api-version', '1.1')
GnomeKeyring.Attribute.list_append_string(attrs, 'keyid', keyid)
return attrs
# Migrates pre Mailnag 1.1 keyring items into the new format
def _migrate_keyring(self):
attrs = GnomeKeyring.Attribute.list_new()
GnomeKeyring.Attribute.list_append_string(attrs, 'application', 'Mailnag')
result, items = GnomeKeyring.find_items_sync(GnomeKeyring.ItemType.GENERIC_SECRET, attrs)
if result == GnomeKeyring.Result.OK:
for i in items:
result, info = GnomeKeyring.item_get_info_sync(self._defaultKeyring, i.item_id)
self.set(info.get_display_name(), i.secret)
GnomeKeyring.item_delete_sync(self._defaultKeyring, i.item_id)
#
# Exception thrown if the GNOME keyring can't be unlocked
#
class KeyringUnlockException(Exception):
def __init__(self, message):
Exception.__init__(self, message)
#
# All supported credential stores
#
SupportedCredentialStores = {
CredentialStoreType.GNOME : GnomeCredentialStore
#CredentialStoreType.KDE : KDECredentialStore
}