Mind Bending

Gnome Keyring

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.

Magnun

Magnun

Graduated in Telecommunication Engineering, but currently working with GNU/Linux infrastructure and in the spare time I'm an Open Source programmer (Python and C), a drawer and author in the Mind Bending Blog.


Comments

comments powered by Disqus