Upgraded to open_id_authentication plugin at 00d8bc7f97 and unpacked ruby-openid gem version 2.1.2.

This commit is contained in:
Eric Allen 2008-12-08 00:44:09 -05:00
parent 6149900e0c
commit e92dae2ffc
227 changed files with 30857 additions and 669 deletions

View file

@ -0,0 +1,271 @@
require 'fileutils'
require 'pathname'
require 'tempfile'
require 'openid/util'
require 'openid/store/interface'
require 'openid/association'
module OpenID
module Store
class Filesystem < Interface
@@FILENAME_ALLOWED = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ.-".split("")
# Create a Filesystem store instance, putting all data in +directory+.
def initialize(directory)
p_dir = Pathname.new(directory)
@nonce_dir = p_dir.join('nonces')
@association_dir = p_dir.join('associations')
@temp_dir = p_dir.join('temp')
self.ensure_dir(@nonce_dir)
self.ensure_dir(@association_dir)
self.ensure_dir(@temp_dir)
end
# Create a unique filename for a given server url and handle. The
# filename that is returned will contain the domain name from the
# server URL for ease of human inspection of the data dir.
def get_association_filename(server_url, handle)
unless server_url.index('://')
raise ArgumentError, "Bad server URL: #{server_url}"
end
proto, rest = server_url.split('://', 2)
domain = filename_escape(rest.split('/',2)[0])
url_hash = safe64(server_url)
if handle
handle_hash = safe64(handle)
else
handle_hash = ''
end
filename = [proto,domain,url_hash,handle_hash].join('-')
@association_dir.join(filename)
end
# Store an association in the assoc directory
def store_association(server_url, association)
assoc_s = association.serialize
filename = get_association_filename(server_url, association.handle)
f, tmp = mktemp
begin
begin
f.write(assoc_s)
f.fsync
ensure
f.close
end
begin
File.rename(tmp, filename)
rescue Errno::EEXIST
begin
File.unlink(filename)
rescue Errno::ENOENT
# do nothing
end
File.rename(tmp, filename)
end
rescue
self.remove_if_present(tmp)
raise
end
end
# Retrieve an association
def get_association(server_url, handle=nil)
# the filename with empty handle is the prefix for the associations
# for a given server url
filename = get_association_filename(server_url, handle)
if handle
return _get_association(filename)
end
assoc_filenames = Dir.glob(filename.to_s + '*')
assocs = assoc_filenames.collect do |f|
_get_association(f)
end
assocs = assocs.find_all { |a| not a.nil? }
assocs = assocs.sort_by { |a| a.issued }
return nil if assocs.empty?
return assocs[-1]
end
def _get_association(filename)
begin
assoc_file = File.open(filename, "r")
rescue Errno::ENOENT
return nil
else
begin
assoc_s = assoc_file.read
ensure
assoc_file.close
end
begin
association = Association.deserialize(assoc_s)
rescue
self.remove_if_present(filename)
return nil
end
# clean up expired associations
if association.expires_in == 0
self.remove_if_present(filename)
return nil
else
return association
end
end
end
# Remove an association if it exists, otherwise do nothing.
def remove_association(server_url, handle)
assoc = get_association(server_url, handle)
if assoc.nil?
return false
else
filename = get_association_filename(server_url, handle)
return self.remove_if_present(filename)
end
end
# Return whether the nonce is valid
def use_nonce(server_url, timestamp, salt)
return false if (timestamp - Time.now.to_i).abs > Nonce.skew
if server_url and !server_url.empty?
proto, rest = server_url.split('://',2)
else
proto, rest = '',''
end
raise "Bad server URL" unless proto && rest
domain = filename_escape(rest.split('/',2)[0])
url_hash = safe64(server_url)
salt_hash = safe64(salt)
nonce_fn = '%08x-%s-%s-%s-%s'%[timestamp, proto, domain, url_hash, salt_hash]
filename = @nonce_dir.join(nonce_fn)
begin
fd = File.new(filename, File::CREAT | File::EXCL | File::WRONLY, 0200)
fd.close
return true
rescue Errno::EEXIST
return false
end
end
# Remove expired entries from the database. This is potentially expensive,
# so only run when it is acceptable to take time.
def cleanup
cleanup_associations
cleanup_nonces
end
def cleanup_associations
association_filenames = Dir[@association_dir.join("*").to_s]
count = 0
association_filenames.each do |af|
begin
f = File.open(af, 'r')
rescue Errno::ENOENT
next
else
begin
assoc_s = f.read
ensure
f.close
end
begin
association = OpenID::Association.deserialize(assoc_s)
rescue StandardError
self.remove_if_present(af)
next
else
if association.expires_in == 0
self.remove_if_present(af)
count += 1
end
end
end
end
return count
end
def cleanup_nonces
nonces = Dir[@nonce_dir.join("*").to_s]
now = Time.now.to_i
count = 0
nonces.each do |filename|
nonce = filename.split('/')[-1]
timestamp = nonce.split('-', 2)[0].to_i(16)
nonce_age = (timestamp - now).abs
if nonce_age > Nonce.skew
self.remove_if_present(filename)
count += 1
end
end
return count
end
protected
# Create a temporary file and return the File object and filename.
def mktemp
f = Tempfile.new('tmp', @temp_dir)
[f, f.path]
end
# create a safe filename from a url
def filename_escape(s)
s = '' if s.nil?
filename_chunks = []
s.split('').each do |c|
if @@FILENAME_ALLOWED.index(c)
filename_chunks << c
else
filename_chunks << sprintf("_%02X", c[0])
end
end
filename_chunks.join("")
end
def safe64(s)
s = OpenID::CryptUtil.sha1(s)
s = OpenID::Util.to_base64(s)
s.gsub!('+', '_')
s.gsub!('/', '.')
s.gsub!('=', '')
return s
end
# remove file if present in filesystem
def remove_if_present(filename)
begin
File.unlink(filename)
rescue Errno::ENOENT
return false
end
return true
end
# ensure that a path exists
def ensure_dir(dir_name)
FileUtils::mkdir_p(dir_name)
end
end
end
end

View file

@ -0,0 +1,75 @@
require 'openid/util'
module OpenID
# Stores for Associations and nonces. Used by both the Consumer and
# the Server. If you have a database abstraction layer or other
# state storage in your application or framework already, you can
# implement the store interface.
module Store
# Abstract Store
# Changes in 2.0:
# * removed store_nonce, get_auth_key, is_dumb
# * changed use_nonce to support one-way nonces
# * added cleanup_nonces, cleanup_associations, cleanup
class Interface < Object
# Put a Association object into storage.
# When implementing a store, don't assume that there are any limitations
# on the character set of the server_url. In particular, expect to see
# unescaped non-url-safe characters in the server_url field.
def store_association(server_url, association)
raise NotImplementedError
end
# Returns a Association object from storage that matches
# the server_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)
raise NotImplementedError
end
# If there is a matching association, remove it from the store and
# return true, otherwise return false.
def remove_association(server_url, handle)
raise NotImplementedError
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.
# server_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)
raise NotImplementedError
end
# Remove expired nonces from the store
# Discards any nonce that is old enough that it wouldn't pass use_nonce
# Not called during normal library operation, this method is for store
# admins to keep their storage from filling up with expired data
def cleanup_nonces
raise NotImplementedError
end
# Remove expired associations from the store
# Not called during normal library operation, this method is for store
# admins to keep their storage from filling up with expired data
def cleanup_associations
raise NotImplementedError
end
# Remove expired nonces and associations from the store
# Not called during normal library operation, this method is for store
# admins to keep their storage from filling up with expired data
def cleanup
return cleanup_nonces, cleanup_associations
end
end
end
end

View file

@ -0,0 +1,84 @@
require 'openid/store/interface'
module OpenID
module Store
# An in-memory implementation of Store. This class is mainly used
# for testing, though it may be useful for long-running single
# process apps. Note that this store is NOT thread-safe.
#
# You should probably be looking at OpenID::Store::Filesystem
class Memory < Interface
def initialize
@associations = {}
@associations.default = {}
@nonces = {}
end
def store_association(server_url, assoc)
assocs = @associations[server_url]
@associations[server_url] = assocs.merge({assoc.handle => deepcopy(assoc)})
end
def get_association(server_url, handle=nil)
assocs = @associations[server_url]
assoc = nil
if handle
assoc = assocs[handle]
else
assoc = assocs.values.sort{|a,b| a.issued <=> b.issued}[-1]
end
return assoc
end
def remove_association(server_url, handle)
assocs = @associations[server_url]
if assocs.delete(handle)
return true
else
return false
end
end
def use_nonce(server_url, timestamp, salt)
return false if (timestamp - Time.now.to_i).abs > Nonce.skew
nonce = [server_url, timestamp, salt].join('')
return false if @nonces[nonce]
@nonces[nonce] = timestamp
return true
end
def cleanup_associations
count = 0
@associations.each{|server_url, assocs|
assocs.each{|handle, assoc|
if assoc.expires_in == 0
assocs.delete(handle)
count += 1
end
}
}
return count
end
def cleanup_nonces
count = 0
now = Time.now.to_i
@nonces.each{|nonce, timestamp|
if (timestamp - now).abs > Nonce.skew
@nonces.delete(nonce)
count += 1
end
}
return count
end
protected
def deepcopy(o)
Marshal.load(Marshal.dump(o))
end
end
end
end

View file

@ -0,0 +1,68 @@
require 'openid/cryptutil'
require 'date'
require 'time'
module OpenID
module Nonce
DEFAULT_SKEW = 60*60*5
TIME_FMT = '%Y-%m-%dT%H:%M:%SZ'
TIME_STR_LEN = '0000-00-00T00:00:00Z'.size
@@NONCE_CHRS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
TIME_VALIDATOR = /\d\d\d\d-\d\d-\d\dT\d\d:\d\d:\d\dZ/
@skew = DEFAULT_SKEW
# The allowed nonce time skew in seconds. Defaults to 5 hours.
# Used for checking nonce validity, and by stores' cleanup methods.
def Nonce.skew
@skew
end
def Nonce.skew=(new_skew)
@skew = new_skew
end
# Extract timestamp from a nonce string
def Nonce.split_nonce(nonce_str)
timestamp_str = nonce_str[0...TIME_STR_LEN]
raise ArgumentError if timestamp_str.size < TIME_STR_LEN
raise ArgumentError unless timestamp_str.match(TIME_VALIDATOR)
ts = Time.parse(timestamp_str).to_i
raise ArgumentError if ts < 0
return ts, nonce_str[TIME_STR_LEN..-1]
end
# Is the timestamp that is part of the specified nonce string
# within the allowed clock-skew of the current time?
def Nonce.check_timestamp(nonce_str, allowed_skew=nil, now=nil)
allowed_skew = skew if allowed_skew.nil?
begin
stamp, foo = split_nonce(nonce_str)
rescue ArgumentError # bad timestamp
return false
end
now = Time.now.to_i unless now
# times before this are too old
past = now - allowed_skew
# times newer than this are too far in the future
future = now + allowed_skew
return (past <= stamp and stamp <= future)
end
# generate a nonce with the specified timestamp (defaults to now)
def Nonce.mk_nonce(time = nil)
salt = CryptUtil::random_string(6, @@NONCE_CHRS)
if time.nil?
t = Time.now.getutc
else
t = Time.at(time).getutc
end
time_str = t.strftime(TIME_FMT)
return time_str + salt
end
end
end