Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Added MemCacheStore option.
  • Loading branch information
josh committed May 12, 2008
1 parent f6f7eac commit 2a6401b
Show file tree
Hide file tree
Showing 3 changed files with 241 additions and 14 deletions.
31 changes: 17 additions & 14 deletions lib/open_id_authentication.rb
@@ -1,7 +1,10 @@
require 'uri'
require 'openid/extensions/sreg'
require 'openid/store/filesystem'
require File.join(File.dirname(__FILE__), 'open_id_authentication/timeout_fixes') if OpenID::VERSION == "2.0.4"

require File.dirname(__FILE__) + '/open_id_authentication/db_store'
require File.dirname(__FILE__) + '/open_id_authentication/mem_cache_store'
require File.dirname(__FILE__) + '/open_id_authentication/timeout_fixes' if OpenID::VERSION == "2.0.4"

module OpenIdAuthentication
OPEN_ID_AUTHENTICATION_DIR = RAILS_ROOT + "/tmp/openids"
Expand All @@ -10,8 +13,19 @@ def self.store
@@store
end

def self.store=(value)
@@store = value
def self.store=(*store_option)
store, *parameters = *([ store_option ].flatten)

@@store = case store
when :db
OpenIdAuthentication::DbStore.new
when :mem_cache
OpenIdAuthentication::MemCacheStore.new(*parameters)
when :file
OpenID::Store::Filesystem.new(OPEN_ID_AUTHENTICATION_DIR)
else
raise "Unknown store: #{store}"
end
end

self.store = :db
Expand Down Expand Up @@ -123,17 +137,6 @@ def open_id_consumer
OpenID::Consumer.new(session, open_id_store)
end

def open_id_store
case store
when :db
OpenIdAuthentication::DbStore.new
when :file
OpenID::FilesystemStore.new(OPEN_ID_AUTHENTICATION_DIR)
else
raise "Unknown store: #{store}"
end
end

def add_simple_registration_fields(open_id_request, fields)
sreg_request = OpenID::SReg::Request.new
sreg_request.request_fields(Array(fields[:required]).map(&:to_s), true) if fields[:required]
Expand Down
73 changes: 73 additions & 0 deletions lib/open_id_authentication/mem_cache_store.rb
@@ -0,0 +1,73 @@
require 'digest/sha1'
require 'openid/store/interface'

module OpenIdAuthentication
class MemCacheStore < OpenID::Store::Interface
def initialize(*addresses)
@connection = ActiveSupport::Cache::MemCacheStore.new(addresses)
end

def store_association(server_url, assoc)
server_key = association_server_key(server_url)
assoc_key = association_key(server_url, assoc.handle)

assocs = @connection.read(server_key) || {}
assocs[assoc.issued] = assoc_key

@connection.write(server_key, assocs)
@connection.write(assoc_key, assoc, :expires_in => assoc.lifetime)
end

def get_association(server_url, handle = nil)
if handle
@connection.read(association_key(server_url, handle))
else
server_key = association_server_key(server_url)
assocs = @connection.read(server_key)
return if assocs.nil?

last_key = assocs[assocs.keys.sort.last]
@connection.read(last_key)
end
end

def remove_association(server_url, handle)
server_key = association_server_key(server_url)
assoc_key = association_key(server_url, handle)
assocs = @connection.read(server_key)

return false unless assocs && assocs.has_value?(assoc_key)

assocs = assocs.delete_if { |key, value| value == assoc_key }

@connection.write(server_key, assocs)
@connection.delete(assoc_key)

return true
end

def use_nonce(server_url, timestamp, salt)
return false if @connection.read(nonce_key(server_url, salt))
return false if (timestamp - Time.now.to_i).abs > OpenID::Nonce.skew
@connection.write(nonce_key(server_url, salt), timestamp, :expires_in => OpenID::Nonce.skew)
return true
end

private
def association_key(server_url, handle = nil)
"openid_association_#{digest(server_url)}_#{digest(handle)}"
end

def association_server_key(server_url)
"openid_association_server_#{digest(server_url)}"
end

def nonce_key(server_url, salt)
"openid_nonce_#{digest(server_url)}_#{digest(salt)}"
end

def digest(text)
Digest::SHA1.hexdigest(text)
end
end
end
151 changes: 151 additions & 0 deletions test/mem_cache_store_test.rb
@@ -0,0 +1,151 @@
require File.dirname(__FILE__) + '/test_helper'
require File.dirname(__FILE__) + '/../lib/open_id_authentication/mem_cache_store'

# Mock MemCacheStore with MemoryStore for testing
class OpenIdAuthentication::MemCacheStore < OpenID::Store::Interface
def initialize(*addresses)
@connection = ActiveSupport::Cache::MemoryStore.new
end
end

class MemCacheStoreTest < Test::Unit::TestCase
ALLOWED_HANDLE = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~'

def setup
@store = OpenIdAuthentication::MemCacheStore.new
end

def test_store
server_url = "http://www.myopenid.com/openid"
assoc = gen_assoc(0)

# Make sure that a missing association returns no result
assert_retrieve(server_url)

# Check that after storage, getting returns the same result
@store.store_association(server_url, assoc)
assert_retrieve(server_url, nil, assoc)

# more than once
assert_retrieve(server_url, nil, assoc)

# Storing more than once has no ill effect
@store.store_association(server_url, assoc)
assert_retrieve(server_url, nil, assoc)

# Removing an association that does not exist returns not present
assert_remove(server_url, assoc.handle + 'x', false)

# Removing an association that does not exist returns not present
assert_remove(server_url + 'x', assoc.handle, false)

# Removing an association that is present returns present
assert_remove(server_url, assoc.handle, true)

# but not present on subsequent calls
assert_remove(server_url, assoc.handle, false)

# Put assoc back in the store
@store.store_association(server_url, assoc)

# More recent and expires after assoc
assoc2 = gen_assoc(1)
@store.store_association(server_url, assoc2)

# After storing an association with a different handle, but the
# same server_url, the handle with the later expiration is returned.
assert_retrieve(server_url, nil, assoc2)

# We can still retrieve the older association
assert_retrieve(server_url, assoc.handle, assoc)

# Plus we can retrieve the association with the later expiration
# explicitly
assert_retrieve(server_url, assoc2.handle, assoc2)

# More recent, and expires earlier than assoc2 or assoc. Make sure
# that we're picking the one with the latest issued date and not
# taking into account the expiration.
assoc3 = gen_assoc(2, 100)
@store.store_association(server_url, assoc3)

assert_retrieve(server_url, nil, assoc3)
assert_retrieve(server_url, assoc.handle, assoc)
assert_retrieve(server_url, assoc2.handle, assoc2)
assert_retrieve(server_url, assoc3.handle, assoc3)

assert_remove(server_url, assoc2.handle, true)

assert_retrieve(server_url, nil, assoc3)
assert_retrieve(server_url, assoc.handle, assoc)
assert_retrieve(server_url, assoc2.handle, nil)
assert_retrieve(server_url, assoc3.handle, assoc3)

assert_remove(server_url, assoc2.handle, false)
assert_remove(server_url, assoc3.handle, true)

assert_retrieve(server_url, nil, assoc)
assert_retrieve(server_url, assoc.handle, assoc)
assert_retrieve(server_url, assoc2.handle, nil)
assert_retrieve(server_url, assoc3.handle, nil)

assert_remove(server_url, assoc2.handle, false)
assert_remove(server_url, assoc.handle, true)
assert_remove(server_url, assoc3.handle, false)

assert_retrieve(server_url, nil, nil)
assert_retrieve(server_url, assoc.handle, nil)
assert_retrieve(server_url, assoc2.handle, nil)
assert_retrieve(server_url, assoc3.handle, nil)

assert_remove(server_url, assoc2.handle, false)
assert_remove(server_url, assoc.handle, false)
assert_remove(server_url, assoc3.handle, false)
end

def test_nonce
server_url = "http://www.myopenid.com/openid"

[server_url, ''].each do |url|
nonce1 = OpenID::Nonce::mk_nonce

assert_nonce(nonce1, true, url, "#{url}: nonce allowed by default")
assert_nonce(nonce1, false, url, "#{url}: nonce not allowed twice")
assert_nonce(nonce1, false, url, "#{url}: nonce not allowed third time")

# old nonces shouldn't pass
old_nonce = OpenID::Nonce::mk_nonce(3600)
assert_nonce(old_nonce, false, url, "Old nonce #{old_nonce.inspect} passed")
end
end

private
def gen_assoc(issued, lifetime = 600)
secret = OpenID::CryptUtil.random_string(20, nil)
handle = OpenID::CryptUtil.random_string(128, ALLOWED_HANDLE)
OpenID::Association.new(handle, secret, Time.now + issued, lifetime, 'HMAC-SHA1')
end

def assert_retrieve(url, handle = nil, expected = nil)
assoc = @store.get_association(url, handle)

if expected.nil?
assert_nil(assoc)
else
assert_equal(expected, assoc)
assert_equal(expected.handle, assoc.handle)
assert_equal(expected.secret, assoc.secret)
end
end

def assert_remove(url, handle, expected)
present = @store.remove_association(url, handle)
assert_equal(expected, present)
end

def assert_nonce(nonce, expected, server_url, msg = "")
stamp, salt = OpenID::Nonce::split_nonce(nonce)
actual = @store.use_nonce(server_url, stamp, salt)
assert_equal(expected, actual, msg)
end
end

0 comments on commit 2a6401b

Please sign in to comment.