Create a gist now

Instantly share code, notes, and snippets.

What would you like to do?
Patching ruby-openid to work under GAE

Initial Steps

First of all, uninstall any jruby-openssl gems you may have as they don't work under GAE:

sudo gem uninstall jruby-openssl

After that download and install the jruby-openssl gem that is patched to work under jruby and GAE:

curl -O http://rc-gems.appspot.com/gems/jruby-openssl-0.5.2.gem
sudo gem install jruby-openssl-0.5.2.gem

After you've installed the patched jruby-openssl gem, require it in your Gemfile like this (using the version is mandatory I'm afraid):

# List gems to bundle here:
gem 'jruby-openssl', '0.5.2'
gem 'ruby-openid', '2.1.8'
gem 'rails_dm_datastore'

That's all you have to do prerequisites-wise.

Now, the solution I've prepared is working for rails (2.3.5), it'll propably work for sinatra, etc but since I'm fairly new to the ruby world I can't prepare a copy-paste solution for that atm (anyone cares to do that?)

In order to patch ruby-openid create /config/initializers/ruby_openid_patch.rb and paste inside the contents of the file you'll find in the gist below.

Now, wherever you instantiate an OpenID::Consumer class make sure that you also pass a GaeStore variable as it's store.

def consumer
  if @consumer.nil?
    store = GaeStore.new
    @consumer = OpenID::Consumer.new(session, store)
  end
  return @consumer
end
if defined? JRuby::Rack
require 'openid'
require 'openid/store/interface'
require 'dm-core'
require 'net/http'
require 'appengine-apis/urlfetch'
OpenID::Util.logger = Rails.logger
# Fetcher class compatible with GAE
class MyFetcher < OpenID::StandardFetcher
def fetch(url, body=nil, headers=nil, redirect_limit=REDIRECT_LIMIT)
raise OpenID::FetchingError, "Blank URL: #{url}" if url.blank?
headers ||= {}
headers['User-agent'] ||= USER_AGENT
options = {
:follow_redirects => true,
:allow_truncated => true,
:headers => headers
}
response = nil
if not body.nil?
options[:method] = 'POST'
options[:headers]["Content-type"] ||= "application/x-www-form-urlencoded"
options[:payload] = body # Rack::Utils.build_query(body)
else
options[:method] = 'GET'
end
begin
response = AppEngine::URLFetch.fetch(url, options)
rescue Exception => why
raise OpenID::FetchingError, "Error fetching #{url}: #{why}"
end
return OpenID::HTTPResponse._from_net_response(response, url)
end
end
OpenID.fetcher = MyFetcher.new
class MyAssociation
include DataMapper::Resource
property :id, Serial
property :handle, String
property :secret, Blob
property :issued, Integer
property :lifetime, Integer
property :assoc_type, String
property :srv_url, String
def from_record
assoc = OpenID::Association.new(handle, secret, issued, lifetime, assoc_type)
end
end
class MyNonce
include DataMapper::Resource
property :id, Serial
property :salt, String, :required => true
property :srv_url, String, :required => true
property :timestamp, Integer, :required => true
end
class GaeStore < OpenID::Store::Interface
# Put a Association object into storage.
# When implementing a store, don't assume that there are any limitations
# on the character set of the srv_url. In particular, expect to see
# unescaped non-url-safe characters in the srv_url field.
def store_association(server_url, assoc)
# warn "->store_association('#{server_url}', '#{assoc.handle}')"
remove_association(server_url, assoc.handle)
a = MyAssociation.new(
:srv_url => server_url,
:handle => assoc.handle,
:secret => assoc.secret,
:issued => assoc.issued,
:lifetime => assoc.lifetime,
:assoc_type => assoc.assoc_type
)
a.save
end
# Returns a Association object from storage that matches
# the srv_url. Returns nil if no such association is found or if
# the one matching association is expired. (Is allowed to GC expired
# associations when found.)
def get_association(server_url, handle=nil)
assocs = if handle.nil?
MyAssociation.all(:srv_url => server_url)
else
MyAssociation.all(:srv_url => server_url, :handle => handle)
end
assocs.each do |assoc|
a = assoc.from_record
if a.expires_in <= 0
assoc.destroy!
else
return a
end
end if assocs.any?
return nil
end
# If there is a matching association, remove it from the store and
# return true, otherwise return false.
def remove_association(server_url, handle)
assocs = MyAssociation.all(:srv_url => server_url, :handle => handle).destroy!
end
# Return true if the nonce has not been used before, and store it
# for a while to make sure someone doesn't try to use the same value
# again. Return false if the nonce has already been used or if the
# timestamp is not current.
# You can use OpenID::Store::Nonce::SKEW for your timestamp window.
# srv_url: URL of the server from which the nonce originated
# timestamp: time the nonce was created in seconds since unix epoch
# salt: A random string that makes two nonces issued by a server in
# the same second unique
def use_nonce(server_url, timestamp, salt)
return false if MyNonce.first(:srv_url => server_url, :timestamp => timestamp, :salt => salt)
return false if (timestamp - Time.now.to_i).abs > OpenID::Nonce.skew
n = MyNonce.new(:srv_url => server_url, :timestamp => timestamp, :salt => salt)
n.save
return true
end
def cleanup_nonces
now = Time.now.to_i
MyNonce.all(:timestamp.gt => now + OpenID::Nonce.skew).destroy!
MyNonce.all(:timestamp.lt => now - OpenID::Nonce.skew).destroy!
end
def cleanup_associations
now = Time.now.to_i
assocs = MyAssociation.all
assocs.each do |a|
if a.issued + a.lifetime < now
a.destroy!
end
end
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment