Keychain, Objective-C and Python
Something I’ve been playing about with today and yesterday with is the Objective-C bindings for Python, PyObjC. The purpose of this is pretty simple - Python is a great language for writing glue code. One of these instances is a script I wrote a while back to let me use Quicksilver to post tasks to Remember the Milk, the excellent online to-do list manager.
The problem with interacting with some online services is the issue of authentication. Ideally, you don’t want to store a password inside your application, nor do you want to store it in a preference file. Reinventing the wheel is boring. OS X already has a password management utility - the Keychain (which has some really cool functions, so open up Keychain Access.app! If you are a Firefox user like myself, 1Passwd is a commercial OS X application that’ll hook your Firefox use into a separate Keychain).
After a bit of Googling, I found this blog post with some guides on how to use Keychain in Python. I’ve experimented a lot with it, so I thought I’d expand this post.
The prerequisites for Keychain hacking are simple. You need PyObjC. You can download a .dmg file from the website if you use the standard version of Python built in to 10.4 (Python 2.4), but if, like me, you use Python 2.5, you should download the source release, untar it and run:
sudo python setup.py install
You can ensure that PyObjC is installed by popping open a python shell and typing import objc. If you get an error, it’s not worked properly.
The other prerequisite is Keychain.framework. This is a bit of a pain to setup. I set it up by opening the Xcode Project file in Xcode and then building it in there. Then I somehow managed to find the framework folder in the Executables panel or something. As it’s open source, I can always put up a downloadable framework folder if it’s not working.
Once you have Keychain.framework installed in /Library/Frameworks, you can check it’s installed by going into a python shell and typing the following:
import objc, new
Keychain = new.module('Keychain')
objc.loadBundle('Keychain', Keychain.__dict__, bundle_path='/Library/Frameworks/Keychain.framework')
This creates a new module called Keychain, which loads in all the Objective-C modules as classes. You can get documentation about these classes in the Framework itself from header files. Those are the files that end in .h. The ones which are most useful are Keychain.h, KeychainItem.h and KeychainSearch.h. You can also get details about these modules from within a python shell, by typing help(Keychain.Keychain), where the second one is the class you want (eg. help(Keychain.KeychainSearch) etc.). The Python documentation gives you some of the details you need for most of the functions, but it doesn’t cover which arguments they take. For that, reading the header files is useful. Also, Google function names, or look at this documentation (which doesn’t always seem adequate).
Now, for building a script like I described above, to access a web service, you can tap into Keychain by letting the user login to a site in Safari (or another WebKit browser that supports Keychain). Once they’ve logged in and stored their password in Keychain, you can access their username and password programatically using the KeychainSearch class. You instantiate the KeychainSearch like this:
search = Keychain.KeychainSearch.new()
I’ve written a template function which you can use to programatically get to the Keychain for web passwords:
def getRememberTheMilkAccount():
Keychain = new.module('Keychain')
objc.loadBundle('Keychain', Keychain.__dict__, bundle_path='/Library/Frameworks/Keychain.framework')
search = Keychain.KeychainSearch.new()
for i in search.internetSearchResults():
if str(i).startswith("rememberthemilk.com") is True:
keyi = i
return keyi
This function instantiates the Keychain, loads in the Keychain framework, creates a new KeychainSearch called (imaginatively) ‘search’, then loops through all the Internet keys it has searching for one which starts ‘rememberthemilk.com’ (you may need to check that it’s not www.rememberthemilk.com, or whatever it is for the site you are accessing), then if it finds it, returns it.
This will return the Keychain item. The two most useful functions for that are ‘account’ and ‘dataAsString’. Account is the username or email address or whatever you use to sign in to that site, and dataAsString returns the password. When you try and run dataAsString(), Keychain should ask the user for confirmation. This takes a number of steps. Firstly, if they Keychain is locked, the user will be asked to unlock it with a password. Secondly, they will be asked whether they wish to allow Python to access this Keychain item - and they can Deny, Allow Once or Allow Always. It looks like this.
Here’s the code you use to get the data out of the function above:
account = getRememberTheMilkAccount()
username = account.account()
password = account.dataAsString()
Once you have their username and password out of the Keychain, you can start making calls to the relevant APIs. This is scratching the surface of what’s available with the Objective-C APIs that PyObjC makes available to Python users. Ruby users out there have RubyObjC to play with. I did briefly try it out in irb (you can install it with sudo gem install rubyobjc), but didn’t get very far. You can load in the Keychain framework by following the instructions in section 2.2 of this page. Once you’ve installed RubyObjC, you can call the Keychain framework like this:
require 'rubygems'
require 'objc'
ObjC::NSBundle.bundleWithPath_( "/Library/Frameworks/Keychain.framework" ).load
puts ObjC::Keychain.methods
The difference between the Python and Ruby way of doing it is that in Python, Keychain becomes a child of the main Keychain module, while in Ruby, you use the ObjC:: prefix for all the modules. So, instead of Keychain.KeychainSearch, you use ObjC::KeychainSearch. There is more to it than this, and I’ll leave the Rubyists to find out more. RubyObjC, like PyObjC, is quite a complex library to get your head around.