In a couple of posts ago I talked about two ways to solve a Gnome Keyring issue in Python. The fist way (demonstrated here), was to create o simple class that wraps the libgnome-keyring and in each request it lock and unlock the Gnome Keyring. It wasn’t perfect and had some security issues but at least kept your Keyring securely locked. The second solution, and also the most elegant, is to create a class that wraps the libgnome-keyring and mimics the idle timeout.
In this post I’ll show an example of implementation. The code presented here is based in my last post code. I just changed a few lines and added a GTK Window that mimics an application. Let’s see the code:
# Written by Magnun Leno. License: GPLv3
import gnomekeyring as gk
import glib
import gtk
from gobject import timeout_add, source_remove
APP_NAME = 'MyApp'
IDLE_TIME = 0.1 # In minutes
glib.set_application_name(APP_NAME)
if not gk.is_available():
print 'Gnome Keyring is unavailable!'
exit()
class KeyringManager:
'''
The KeyringManager class wraps the main functions used to
manage keyrings.
'''
def __init__(self):
'''
Load the basic informations:
- Keyring names;
- default keyring.
'''
self.default_keyring = gk.get_default_keyring_sync()
self.keyrings = gk.list_keyring_names_sync()
def __update_keyring_names(when):
'''
Descriptor used to auto update some attributes in the
KeyringManager class. Currently it just reloads the
keyring names but can be expanded to update any other
information.
'''
def func(f):
def func_params(self, *args, **kwargs):
if when == 'before':
self.keyrings = gk.list_keyring_names_sync()
ret = f(self, *args, **kwargs)
if when == 'after':
self.keyrings = gk.list_keyring_names_sync()
return ret
return func_params
return func
@__update_keyring_names('after')
def create_keyring(self, name, password):
'''
Create a keyring. Don't return any value.
'''
gk.create_sync(name, password)
@__update_keyring_names('after')
def delete_keyring(self, name):
'''
Delete a Keyring. Don't return any value
'''
gk.delete_sync(name)
def lock_all_keyrings(self):
'''
Lock all Keyrings. Don't return any value
'''
gk.lock_all_sync()
def set_default_keyring(self, name):
gk.set_default_keyring_sync(name)
self.default_keyring = gk.get_default_keyring_sync()
@__update_keyring_names('before')
def __contains__(self, name):
'''
Return a boolean value based in the existence of an
Keyring with the informed name. can e used with the
in statement:
>>> 'mykeyring' in keyringmanager
True
'''
return name in self.keyrings
@__update_keyring_names('before')
def __getitem__(self, name):
'''
Mimics the dictionary behaviour:
>>> keyringmanager['mykeyring']
'''
if name in self:
return Keyring(name)
return None
class Keyring:
'''
The Keyring class wrapps some basic actions to the Gnome
Keyring and it's items, such as:
- lock;
- unlock;
- list items id;
- change password;
- access it's items;
- get items secret.
'''
def __init__(self, name):
'''
Automatically lock the keyring and starts its basic
informations:
- default idle timeout;
- keyring name;
- keyring info;
- lock on idle flag.
'''
global IDLE_TIME
self.idle_timeout = IDLE_TIME # In minutes
self.name = name
self.info = gk.get_info_sync(name)
if self.info.get_is_locked():
self.__idle_timeout = None
self.lock_on_idle = False
else:
self.__idle_timeout = self.__set_timeout(self.__autolock)
self.lock_on_idle = True
self.lock()
def __set_timeout(self, func, *args):
'''
Auxiliar function. Just returns a gobject timeout.
'''
idle_time = int(self.idle_timeout*1000*60)
timeout = timeout_add(idle_time, func, *args)
return timeout
def __autolock(self):
'''
Function used by the gobject timout to atomically lock
the keyring.
'''
if not self.is_locked():
self.lock()
source_remove(self.__idle_timeout)
self.__idle_timeout = None
self.lock_on_idle = False
def __restart_timeout(self):
'''
Just restarts the timeout in case of any activity.
Must be called by every method that access restricted
information.
'''
source_remove(self.__idle_timeout)
self.__idle_timeout = self.__set_timeout(self.__autolock)
def lock(self):
'''
Just lock the keyring.
'''
gk.lock_sync(self.name)
def unlock(self, passwd):
'''
Just unlock the keyring.
'''
gk.unlock_sync(self.name, passwd)
if self.__idle_timeout is None:
self.lock_on_idle = True
self.__idle_timeout = self.__set_timeout(self.__autolock)
def list_items_id(self):
'''
List items id and restarts the idle timeout.
'''
if self.is_locked():
raise Exception("Keyring '"+self.name+"' is locked")
self.items_id = gk.list_item_ids_sync(self.name)
self.__restart_timeout()
return self.items_id
def is_locked(self):
'''
Informs if the Keyring is locked.
'''
self.update_info()
return self.info.get_is_locked()
def update_info(self):
'''
Update keyring information. Is called by methods that
alters the Keyring information.
'''
self.info = gk.get_info_sync(self.name)
def change_password(self, old_passwd, new_passwd):
'''
Change the Keyring password.
'''
if self.is_locked():
raise Exception("Keyring '"+self.name+"' is locked")
self.__restart_timeout()
gk.change_password_sync(self.name, old_passwd, new_passwd)
return True
def get_item_secret(self, item_id):
'''
Return the secret of a Keyring Item.
'''
if self.is_locked():
raise Exception("Keyring '"+self.name+"' is locked")
item_info = gk.item_get_info_sync(self.name, item_id)
self.__restart_timeout()
return item_info.get_secret()
def __getitem__(self, count):
'''
Used to mimic the dictionary behaviour.
'''
if self.is_locked():
raise Exception("Keyring '"+self.name+"' is locked")
item_id = gk.list_item_ids_sync(self.name)[count]
attr = gk.item_get_attributes_sync(self.name, item_id)
attr['id'] = item_id
self.__restart_timeout()
return attr
class Application:
'''
Simulate an application
'''
def __init__(self):
self.window = gtk.Window(gtk.WINDOW_TOPLEVEL)
self.window.connect("destroy", self.destroy)
self.window.set_border_width(10)
self.vbox = gtk.VBox()
self.window.add(self.vbox)
self.check_btn = gtk.Button("Check Keyring")
self.check_btn.connect("clicked", self.show_status)
self.query_btn = gtk.Button("Query Keyring")
self.query_btn.connect("clicked", self.query_info)
self.vbox.pack_start(self.check_btn)
self.vbox.pack_start(self.query_btn)
self.window.show_all()
self.km = KeyringManager()
self.mykey = self.km['mykeyring']
if self.mykey is None:
self.km.create_keyring('mykeyring', 'mypasswd')
self.mykey = self.km['mykeyring']
self.count = 0
print '-> %3i:'%(self.count),
print 'Keyring:',self.mykey.name,'t Lock:', self.mykey.is_locked()
self.count += 1
if self.mykey.is_locked():
self.mykey.unlock('mypasswd')
self.show_status(None)
def show_status(self, widget):
print '-> %3i:'%(self.count),
print 'Keyring:',self.mykey.name,'t Lock:', self.mykey.is_locked()
self.count += 1
def query_info(self, widget):
print '->',self.mykey.name, 'items:', self.mykey.list_items_id()
def destroy(self, widget, data=None):
gtk.main_quit()
def main(self):
gtk.main()
if __name__ == "__main__":
app = Application()
app.main()
Here I’ve used an GTK Window with 2 buttons (a simple pyGTK Helloworld). The first one prints the keyring name and if it’s locked, the second one query the keyring items and print its ids. This second button restarts the idle timeout. In the beginning of the application it calls the KeyringManager (line 248) and tries to get the ‘mykeyring’ Keyring. In case it doesn’t exist, we create it (line 251). After printing the Keyring status, that probably is ‘locked’, the application unlocks it (in a real application a password dialog must be used to request user input) and print its status again. If keep clicking in ‘Check Keyring’ you’ll notice that eventually the keyring will be automatically locked and a ‘Query Keyring’ will cause an exception. In this example the Keyring timeout is 0.1 minutes (see line 8) or 6 seconds. In a real application this timeout should be something around 5 minutes, to prevent the user become bothered with so many requests to unlock he keyring.
Let’s see an example of it’s output:
$ python KeyringWrapper.py
-> 0: Keyring: mykeyring Lock: True
-> 1: Keyring: mykeyring Lock: False
-> 2: Keyring: mykeyring Lock: False
-> 3: Keyring: mykeyring Lock: False
-> 4: Keyring: mykeyring Lock: False
-> 5: Keyring: mykeyring Lock: False
-> 6: Keyring: mykeyring Lock: True
-> 7: Keyring: mykeyring Lock: True
-> 8: Keyring: mykeyring Lock: True
Probably this is my last post about the basics of Gnome Keyring and Python. I may return to this subject eventually since I’ll deal with it many times in the future, also I’ll re-write this same posts in Portuguese (my mother language). But stay tuned for some more Python hacks and informations about my projects. Any comment or suggestion is welcome.
Comments
comments powered by Disqus