RubyCocoa and Keychain access
- 12 comments
- tagged with ruby, rubycocoa, cocoa, leopard
There have been a few posts asking how to access and manipulate the Keychain from within RubyCocoa but no answers supplied. As someone who is just getting into RubyCocoa (and Cocoa all together for that matter), I thought I’d document once and for all the process I took to get it working.
I am running on Leopard and while 10.5 is supposed to ship with BridgeSupport for most frameworks, the Security framework must have been left out. After playing around with using other frameworks (such as the AddressBook framework) and seeing how they were used, I did a bit of digging in the Apple Developer Documentation (incredible resource for anyone starting out) and stumbled across this gem: Generate Framework Metadata
After reading this, I was accessing the keychain within 5 minutes. Here’s how:
-
Run the following commands (first one will take some time):
1 2 3
gen_bridge_metadata -f Security -o Security.bridgesupport mkdir -p /Library/BridgeSupport mv Security.bridgesupport /Library/BridgeSupport -
Open an IRB session to test it:
1 2 3 4 5 6
>> require 'osx/cocoa' => true >> OSX.require_framework 'Security' => true >> defined? OSX::SecKeychainAddGenericPassword() => "method"
Hoorah! Two steps! Well, one because the second one was just testing… Now go make password-able RubyCocoa application! I am not sure how to pull this off with deployable apps but I suggest your apps could ship with the file or run those commands on first-run or if the Security.bridgesupport file doesn’t exist. Note: I have not tested either of those scenarios, but I would guess the latter would be more reliable for cross-version development (i.e. Tiger + Leopard)
How to use these methods
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
# Require needed libraries
require 'osx/cocoa'
include OSX
require_framework 'Security'
# Set up some relevant variables
service = "Test Service"
account = "MyUsername"
password = "MyPassword"
# Add password to default keychain (first param)
SecKeychainAddGenericPassword(nil, service.length, service, account.length, account, password.length, password, nil)
# Finding a password in default keychain (first param)
status, *password = SecKeychainFindGenericPassword(nil, service.length, service, account.length, account)
# Password-related data
password_length = password.shift # => 10
password_data = password.shift # OSX::ObjcPtr object
password = password_data.bytestr(password_length)
# The last item is another OSX::ObjcPtr. I haven't figured
# out how to cast or use this yet but will post when I've
# figured it out
keychain_item = password.shift
# Yay it works! You can also check the password exists
# in the Keychain Access utility. I've confirmed that
# this works
puts password # => "MyPassword"
[UPDATE 4/8/08]: Below is a fix for the REXML bug some people have been getting (myself included)
There is good news for anyone that has been getting the following error:
1 2 3
undefined local variable or method `trans’ for <UNDEFINED> … </>:REXML::Document
Usage: gen_bridge_metadata [options] <headers…>
Use the `-h’ flag or consult gen_bridge_metadata(1) for help.
There is a very simple fix. This is caused by a typo in the source of REXML. It’s such a massive bug I can’t believe it made it into the release of Ruby but i did some poking around and it looks like they renamed a method and didn’t change all references to it.
There are two ways to fix it—patching REXML; and patching gen_brige_metadata.
Personally I prefer patching the generator for peace of mind and have submitted the patch at http://bridgesupport.macosforge.org/trac/ticket/1, but I’ll show both fixes.
Patching gen_bridge_metadata
- Open /usr/bin/gen_bridge_metadata and find the method definition for generate_xml (should be on line 1531)
- Replace 0 in the xml_document.write() with -1 (both occurrences)
- Save!
Patching REXML
- Open /System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/lib/ruby/1.8/rexml/document.rb
- Find the line that has ‘if trans’ and change it to ‘if transitive’
- Save!
Comments
-
Mathias says:
Thanks a lot! You can actually embed the generated file in your application. Just add a new build phase that copies the .bridgesupport file into Resources/BuildSupport, and Bob's your uncle. -
Bodaniel Jeanes says:
@Mathias But will that copy it on run when other people use the program? Or only when they build it from source? -
Mathias says:
I was referring to using Xcode, which I am at the moment. With a custom build phase the Security.bridgesupport into your application bundle into Resources/BridgeSupport. That way you don't need other people to generate that file on their computers. -
Bodaniel Jeanes says:
Excellent! I had a suspicion that you could do something like that but didn't realize BridgeSupport files would load when inside a bundle's Resources folder. If that works then that's truly fantastic. -
Frank B says:
every time i run gen_bridge_metadata (ugh, i'm tired of even typing it)
i get this error:
undefined local variable or method `trans' for <UNDEFINED> ... </>:REXML::Document
anybody seen this? -
Bodaniel Jeanes says:
I just had a look at the source of gen_bridge_metadata and it seems that it is ruby script. Perhaps you have a broken dependency or a recent upgrade in REXML has renamed a method that this relies on.
If you have done any gem upgrades, perhaps temporarily downgrading might be your solution? -
Frank B says:
thanks for getting back, but i'm going to have to play the newb card
and ask for some hints on how to go about temporarily downgrading. -
Bodaniel Jeanes says:
can you paste in the complete command + output into a paste bin somewhere and put the link here so I can see what might be causing the issue?
EDIT: nevermind, i am getting it too
EDIT 2: I've found a bug report here http://bridgesupport.macosforge.org/trac/ticket/1 and commented to verify that it exists. You should do the same. I'll have a look later tonight and see if I can get the latest version of the script and modify it to work. -
Frank B says:
changing the pound-bang in the beginning of the gen_bridge_metadata to
"/usr/bin/ruby/ fixed it for me i think.
here's where i found it:
http://bridgesupport.macosforge.org/trac/ticket/2
now i've got my own problems trying to get it to work on IOKit or my own frameworks -
Bodaniel Jeanes says:
That didn't work for me at all. I only have /usr/bin/ruby in my path anyway so conflicting ruby installations doesn't explain it. I am still convinced its due to a REXML api change or something.
Back to my original plan of looking through the source and trying to fix it manually -
Bodaniel Jeanes says:
Alright. I have an official fix. I will update the post with details. -
James Chan says:
Regarding the bridge support file, we can use OSX.load_bridge_support_file to load it from any path. I guess it a good idea to embed it in the bundle and load it so don't need to bother installing or copying it to the /Library/BridgeSupport or ~/Library/BridgeSupport.