incubator-heraldry-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From ket...@apache.org
Subject svn commit: r462998 [4/7] - in /incubator/heraldry/libraries/ruby/openid: ./ trunk/ trunk/admin/ trunk/examples/ trunk/examples/rails_active_record_store/ trunk/examples/rails_active_record_store/models/ trunk/examples/rails_openid_login_generator/ tru...
Date Wed, 11 Oct 2006 22:13:10 GMT
Added: incubator/heraldry/libraries/ruby/openid/trunk/examples/rails_server/script/process/reaper
URL: http://svn.apache.org/viewvc/incubator/heraldry/libraries/ruby/openid/trunk/examples/rails_server/script/process/reaper?view=auto&rev=462998
==============================================================================
--- incubator/heraldry/libraries/ruby/openid/trunk/examples/rails_server/script/process/reaper (added)
+++ incubator/heraldry/libraries/ruby/openid/trunk/examples/rails_server/script/process/reaper Wed Oct 11 15:13:01 2006
@@ -0,0 +1,3 @@
+#!/usr/bin/env ruby
+require File.dirname(__FILE__) + '/../../config/boot'
+require 'commands/process/reaper'

Propchange: incubator/heraldry/libraries/ruby/openid/trunk/examples/rails_server/script/process/reaper
------------------------------------------------------------------------------
    svn:executable = *

Added: incubator/heraldry/libraries/ruby/openid/trunk/examples/rails_server/script/process/spawner
URL: http://svn.apache.org/viewvc/incubator/heraldry/libraries/ruby/openid/trunk/examples/rails_server/script/process/spawner?view=auto&rev=462998
==============================================================================
--- incubator/heraldry/libraries/ruby/openid/trunk/examples/rails_server/script/process/spawner (added)
+++ incubator/heraldry/libraries/ruby/openid/trunk/examples/rails_server/script/process/spawner Wed Oct 11 15:13:01 2006
@@ -0,0 +1,3 @@
+#!/usr/bin/env ruby
+require File.dirname(__FILE__) + '/../../config/boot'
+require 'commands/process/spawner'

Propchange: incubator/heraldry/libraries/ruby/openid/trunk/examples/rails_server/script/process/spawner
------------------------------------------------------------------------------
    svn:executable = *

Added: incubator/heraldry/libraries/ruby/openid/trunk/examples/rails_server/script/process/spinner
URL: http://svn.apache.org/viewvc/incubator/heraldry/libraries/ruby/openid/trunk/examples/rails_server/script/process/spinner?view=auto&rev=462998
==============================================================================
--- incubator/heraldry/libraries/ruby/openid/trunk/examples/rails_server/script/process/spinner (added)
+++ incubator/heraldry/libraries/ruby/openid/trunk/examples/rails_server/script/process/spinner Wed Oct 11 15:13:01 2006
@@ -0,0 +1,3 @@
+#!/usr/bin/env ruby
+require File.dirname(__FILE__) + '/../../config/boot'
+require 'commands/process/spinner'

Propchange: incubator/heraldry/libraries/ruby/openid/trunk/examples/rails_server/script/process/spinner
------------------------------------------------------------------------------
    svn:executable = *

Added: incubator/heraldry/libraries/ruby/openid/trunk/examples/rails_server/script/runner
URL: http://svn.apache.org/viewvc/incubator/heraldry/libraries/ruby/openid/trunk/examples/rails_server/script/runner?view=auto&rev=462998
==============================================================================
--- incubator/heraldry/libraries/ruby/openid/trunk/examples/rails_server/script/runner (added)
+++ incubator/heraldry/libraries/ruby/openid/trunk/examples/rails_server/script/runner Wed Oct 11 15:13:01 2006
@@ -0,0 +1,3 @@
+#!/usr/bin/env ruby
+require File.dirname(__FILE__) + '/../config/boot'
+require 'commands/runner'
\ No newline at end of file

Propchange: incubator/heraldry/libraries/ruby/openid/trunk/examples/rails_server/script/runner
------------------------------------------------------------------------------
    svn:executable = *

Added: incubator/heraldry/libraries/ruby/openid/trunk/examples/rails_server/script/server
URL: http://svn.apache.org/viewvc/incubator/heraldry/libraries/ruby/openid/trunk/examples/rails_server/script/server?view=auto&rev=462998
==============================================================================
--- incubator/heraldry/libraries/ruby/openid/trunk/examples/rails_server/script/server (added)
+++ incubator/heraldry/libraries/ruby/openid/trunk/examples/rails_server/script/server Wed Oct 11 15:13:01 2006
@@ -0,0 +1,3 @@
+#!/usr/bin/env ruby
+require File.dirname(__FILE__) + '/../config/boot'
+require 'commands/server'
\ No newline at end of file

Propchange: incubator/heraldry/libraries/ruby/openid/trunk/examples/rails_server/script/server
------------------------------------------------------------------------------
    svn:executable = *

Added: incubator/heraldry/libraries/ruby/openid/trunk/examples/rails_server/test/functional/login_controller_test.rb
URL: http://svn.apache.org/viewvc/incubator/heraldry/libraries/ruby/openid/trunk/examples/rails_server/test/functional/login_controller_test.rb?view=auto&rev=462998
==============================================================================
--- incubator/heraldry/libraries/ruby/openid/trunk/examples/rails_server/test/functional/login_controller_test.rb (added)
+++ incubator/heraldry/libraries/ruby/openid/trunk/examples/rails_server/test/functional/login_controller_test.rb Wed Oct 11 15:13:01 2006
@@ -0,0 +1,18 @@
+require File.dirname(__FILE__) + '/../test_helper'
+require 'login_controller'
+
+# Re-raise errors caught by the controller.
+class LoginController; def rescue_action(e) raise e end; end
+
+class LoginControllerTest < Test::Unit::TestCase
+  def setup
+    @controller = LoginController.new
+    @request    = ActionController::TestRequest.new
+    @response   = ActionController::TestResponse.new
+  end
+
+  # Replace this with your real tests.
+  def test_truth
+    assert true
+  end
+end

Added: incubator/heraldry/libraries/ruby/openid/trunk/examples/rails_server/test/functional/server_controller_test.rb
URL: http://svn.apache.org/viewvc/incubator/heraldry/libraries/ruby/openid/trunk/examples/rails_server/test/functional/server_controller_test.rb?view=auto&rev=462998
==============================================================================
--- incubator/heraldry/libraries/ruby/openid/trunk/examples/rails_server/test/functional/server_controller_test.rb (added)
+++ incubator/heraldry/libraries/ruby/openid/trunk/examples/rails_server/test/functional/server_controller_test.rb Wed Oct 11 15:13:01 2006
@@ -0,0 +1,18 @@
+require File.dirname(__FILE__) + '/../test_helper'
+require 'server_controller'
+
+# Re-raise errors caught by the controller.
+class ServerController; def rescue_action(e) raise e end; end
+
+class ServerControllerTest < Test::Unit::TestCase
+  def setup
+    @controller = ServerController.new
+    @request    = ActionController::TestRequest.new
+    @response   = ActionController::TestResponse.new
+  end
+
+  # Replace this with your real tests.
+  def test_truth
+    assert true
+  end
+end

Added: incubator/heraldry/libraries/ruby/openid/trunk/examples/rails_server/test/test_helper.rb
URL: http://svn.apache.org/viewvc/incubator/heraldry/libraries/ruby/openid/trunk/examples/rails_server/test/test_helper.rb?view=auto&rev=462998
==============================================================================
--- incubator/heraldry/libraries/ruby/openid/trunk/examples/rails_server/test/test_helper.rb (added)
+++ incubator/heraldry/libraries/ruby/openid/trunk/examples/rails_server/test/test_helper.rb Wed Oct 11 15:13:01 2006
@@ -0,0 +1,28 @@
+ENV["RAILS_ENV"] = "test"
+require File.expand_path(File.dirname(__FILE__) + "/../config/environment")
+require 'test_help'
+
+class Test::Unit::TestCase
+  # Transactional fixtures accelerate your tests by wrapping each test method
+  # in a transaction that's rolled back on completion.  This ensures that the
+  # test database remains unchanged so your fixtures don't have to be reloaded
+  # between every test method.  Fewer database queries means faster tests.
+  #
+  # Read Mike Clark's excellent walkthrough at
+  #   http://clarkware.com/cgi/blosxom/2005/10/24#Rails10FastTesting
+  #
+  # Every Active Record database supports transactions except MyISAM tables
+  # in MySQL.  Turn off transactional fixtures in this case; however, if you
+  # don't care one way or the other, switching from MyISAM to InnoDB tables
+  # is recommended.
+  self.use_transactional_fixtures = true
+
+  # Instantiated fixtures are slow, but give you @david where otherwise you
+  # would need people(:david).  If you don't want to migrate your existing
+  # test cases which use the @david style and don't mind the speed hit (each
+  # instantiated fixtures translates to a database query per test method),
+  # then set this back to true.
+  self.use_instantiated_fixtures  = false
+
+  # Add more helper methods to be used by all tests here...
+end

Added: incubator/heraldry/libraries/ruby/openid/trunk/gemspec
URL: http://svn.apache.org/viewvc/incubator/heraldry/libraries/ruby/openid/trunk/gemspec?view=auto&rev=462998
==============================================================================
--- incubator/heraldry/libraries/ruby/openid/trunk/gemspec (added)
+++ incubator/heraldry/libraries/ruby/openid/trunk/gemspec Wed Oct 11 15:13:01 2006
@@ -0,0 +1,20 @@
+require 'rubygems'
+
+SPEC = Gem::Specification.new do |s|
+  s.name = `cat admin/library-name`.strip
+  s.version = `darcs changes --tags= | awk '$1 == "tagged" { print $2 }' | head -n 1`.strip
+  s.author = 'Brian Ellin (JanRain, Inc)'
+  s.email = 'brian@janrian.com'
+  s.homepage = 'http://www.openidenabled.com/openid/libraries/ruby'
+  s.platform = Gem::Platform::RUBY
+  s.summary = 'A library for consuming and serving OpenID identities.'
+  files = Dir.glob("{examples,lib,test}/**/*")
+  s.files = files.delete_if {|f| f.include?('_darcs') || f.include?('admin')}
+  s.require_path = 'lib'
+  s.autorequire = 'openid'
+  s.test_file = 'test/runtests.rb'
+  s.has_rdoc = true
+  s.extra_rdoc_files = ['README','INSTALL','COPYING','TODO']
+  s.rdoc_options << '--main' << 'README'
+  s.add_dependency('ruby-yadis', '>= 0.3.3')
+end

Added: incubator/heraldry/libraries/ruby/openid/trunk/lib/hmac-md5.rb
URL: http://svn.apache.org/viewvc/incubator/heraldry/libraries/ruby/openid/trunk/lib/hmac-md5.rb?view=auto&rev=462998
==============================================================================
--- incubator/heraldry/libraries/ruby/openid/trunk/lib/hmac-md5.rb (added)
+++ incubator/heraldry/libraries/ruby/openid/trunk/lib/hmac-md5.rb Wed Oct 11 15:13:01 2006
@@ -0,0 +1,11 @@
+require 'hmac'
+require 'digest/md5'
+
+module HMAC
+  class MD5 < Base
+    def initialize(key = nil)
+      super(Digest::MD5, 64, 16, key)
+    end
+    public_class_method :new, :digest, :hexdigest
+  end
+end

Added: incubator/heraldry/libraries/ruby/openid/trunk/lib/hmac-rmd160.rb
URL: http://svn.apache.org/viewvc/incubator/heraldry/libraries/ruby/openid/trunk/lib/hmac-rmd160.rb?view=auto&rev=462998
==============================================================================
--- incubator/heraldry/libraries/ruby/openid/trunk/lib/hmac-rmd160.rb (added)
+++ incubator/heraldry/libraries/ruby/openid/trunk/lib/hmac-rmd160.rb Wed Oct 11 15:13:01 2006
@@ -0,0 +1,11 @@
+require 'hmac'
+require 'digest/rmd160'
+
+module HMAC
+  class RMD160 < Base
+    def initialize(key = nil)
+      super(Digest::RMD160, 64, 20, key)
+    end
+    public_class_method :new, :digest, :hexdigest
+  end
+end

Added: incubator/heraldry/libraries/ruby/openid/trunk/lib/hmac-sha1.rb
URL: http://svn.apache.org/viewvc/incubator/heraldry/libraries/ruby/openid/trunk/lib/hmac-sha1.rb?view=auto&rev=462998
==============================================================================
--- incubator/heraldry/libraries/ruby/openid/trunk/lib/hmac-sha1.rb (added)
+++ incubator/heraldry/libraries/ruby/openid/trunk/lib/hmac-sha1.rb Wed Oct 11 15:13:01 2006
@@ -0,0 +1,11 @@
+require 'hmac'
+require 'digest/sha1'
+
+module HMAC
+  class SHA1 < Base
+    def initialize(key = nil)
+      super(Digest::SHA1, 64, 20, key)
+    end
+    public_class_method :new, :digest, :hexdigest
+  end
+end

Added: incubator/heraldry/libraries/ruby/openid/trunk/lib/hmac-sha2.rb
URL: http://svn.apache.org/viewvc/incubator/heraldry/libraries/ruby/openid/trunk/lib/hmac-sha2.rb?view=auto&rev=462998
==============================================================================
--- incubator/heraldry/libraries/ruby/openid/trunk/lib/hmac-sha2.rb (added)
+++ incubator/heraldry/libraries/ruby/openid/trunk/lib/hmac-sha2.rb Wed Oct 11 15:13:01 2006
@@ -0,0 +1,25 @@
+require 'hmac'
+require 'digest/sha2'
+
+module HMAC
+  class SHA256 < Base
+    def initialize(key = nil)
+      super(Digest::SHA256, 64, 32, key)
+    end
+    public_class_method :new, :digest, :hexdigest
+  end
+
+  class SHA384 < Base
+    def initialize(key = nil)
+      super(Digest::SHA384, 128, 48, key)
+    end
+    public_class_method :new, :digest, :hexdigest
+  end
+
+  class SHA512 < Base
+    def initialize(key = nil)
+      super(Digest::SHA512, 128, 64, key)
+    end
+    public_class_method :new, :digest, :hexdigest
+  end
+end

Added: incubator/heraldry/libraries/ruby/openid/trunk/lib/hmac.rb
URL: http://svn.apache.org/viewvc/incubator/heraldry/libraries/ruby/openid/trunk/lib/hmac.rb?view=auto&rev=462998
==============================================================================
--- incubator/heraldry/libraries/ruby/openid/trunk/lib/hmac.rb (added)
+++ incubator/heraldry/libraries/ruby/openid/trunk/lib/hmac.rb Wed Oct 11 15:13:01 2006
@@ -0,0 +1,112 @@
+# Copyright (C) 2001  Daiki Ueno <ueno@unixuser.org>
+# This library is distributed under the terms of the Ruby license.
+
+# This module provides common interface to HMAC engines.
+# HMAC standard is documented in RFC 2104:
+#
+#   H. Krawczyk et al., "HMAC: Keyed-Hashing for Message Authentication",
+#   RFC 2104, February 1997
+#
+# These APIs are inspired by JCE 1.2's javax.crypto.Mac interface.
+#
+#   <URL:http://java.sun.com/security/JCE1.2/spec/apidoc/javax/crypto/Mac.html>
+
+module HMAC
+  class Base
+    def initialize(algorithm, block_size, output_length, key)
+      @algorithm = algorithm
+      @block_size = block_size
+      @output_length = output_length
+      @status = STATUS_UNDEFINED
+      @key_xor_ipad = ''
+      @key_xor_opad = ''
+      set_key(key) unless key.nil?
+    end
+
+    private
+    def check_status
+      unless @status == STATUS_INITIALIZED
+	raise RuntimeError,
+	  "The underlying hash algorithm has not yet been initialized."
+      end
+    end
+
+    public
+    def set_key(key)
+      # If key is longer than the block size, apply hash function
+      # to key and use the result as a real key.
+      key = @algorithm.digest(key) if key.size > @block_size
+      key_xor_ipad = "\x36" * @block_size
+      key_xor_opad = "\x5C" * @block_size
+      for i in 0 .. key.size - 1
+	key_xor_ipad[i] ^= key[i]
+	key_xor_opad[i] ^= key[i]
+      end
+      @key_xor_ipad = key_xor_ipad
+      @key_xor_opad = key_xor_opad
+      @md = @algorithm.new
+      @status = STATUS_INITIALIZED
+    end
+
+    def reset_key
+      @key_xor_ipad.gsub!(/./, '?')
+      @key_xor_opad.gsub!(/./, '?')
+      @key_xor_ipad[0..-1] = ''
+      @key_xor_opad[0..-1] = ''
+      @status = STATUS_UNDEFINED
+    end
+
+    def update(text)
+      check_status
+      # perform inner H
+      md = @algorithm.new
+      md.update(@key_xor_ipad)
+      md.update(text)
+      str = md.digest
+      # perform outer H
+      md = @algorithm.new
+      md.update(@key_xor_opad)
+      md.update(str)
+      @md = md
+    end
+    alias << update
+
+    def digest
+      check_status
+      @md.digest
+    end
+
+    def hexdigest
+      check_status
+      @md.hexdigest
+    end
+    alias to_s hexdigest
+
+    # These two class methods below are safer than using above
+    # instance methods combinatorially because an instance will have
+    # held a key even if it's no longer in use.
+    def Base.digest(key, text)
+      begin
+	hmac = self.new(key)
+	hmac.update(text)
+	hmac.digest
+      ensure
+	hmac.reset_key
+      end
+    end
+
+    def Base.hexdigest(key, text)
+      begin
+	hmac = self.new(key)
+	hmac.update(text)
+	hmac.hexdigest
+      ensure
+	hmac.reset_key
+      end
+    end
+
+    private_class_method :new, :digest, :hexdigest
+  end
+
+  STATUS_UNDEFINED, STATUS_INITIALIZED = 0, 1
+end

Added: incubator/heraldry/libraries/ruby/openid/trunk/lib/openid.rb
URL: http://svn.apache.org/viewvc/incubator/heraldry/libraries/ruby/openid/trunk/lib/openid.rb?view=auto&rev=462998
==============================================================================
--- incubator/heraldry/libraries/ruby/openid/trunk/lib/openid.rb (added)
+++ incubator/heraldry/libraries/ruby/openid/trunk/lib/openid.rb Wed Oct 11 15:13:01 2006
@@ -0,0 +1,5 @@
+require "openid/util"
+require "openid/consumer"
+require "openid/server"
+require "openid/stores"
+require "openid/filestore"

Added: incubator/heraldry/libraries/ruby/openid/trunk/lib/openid/association.rb
URL: http://svn.apache.org/viewvc/incubator/heraldry/libraries/ruby/openid/trunk/lib/openid/association.rb?view=auto&rev=462998
==============================================================================
--- incubator/heraldry/libraries/ruby/openid/trunk/lib/openid/association.rb (added)
+++ incubator/heraldry/libraries/ruby/openid/trunk/lib/openid/association.rb Wed Oct 11 15:13:01 2006
@@ -0,0 +1,109 @@
+require 'openid/util'
+
+module OpenID
+
+  # Represents an "association" between a consumer and server, and
+  # is also used for storage of the information exchanged
+  # during the openid.mode='associate' transaction.
+  # This class is used by the both the server and consumer.
+  class Association
+    @@version = '2'
+    @@assoc_keys = [
+                    'version',
+                    'handle',
+                    'secret',
+                    'issued',
+                    'lifetime',
+                    'assoc_type'
+                   ]
+
+    attr_reader :handle, :secret, :issued, :lifetime, :assoc_type
+
+    def Association.from_expires_in(expires_in, handle, secret, assoc_type)
+      issued = Time.now.to_i
+      lifetime = expires_in
+      new(handle, secret, issued, lifetime, assoc_type) 
+    end
+
+    def Association.serialize(assoc)
+      data = [
+              '2',
+              assoc.handle,
+              OpenID::Util.to_base64(assoc.secret),
+              assoc.issued.to_i.to_s,
+              assoc.lifetime.to_i.to_s,
+              assoc.assoc_type              
+             ]
+  
+      lines = ""
+      (0...@@assoc_keys.length).collect do |i| 
+        lines += "#{@@assoc_keys[i]}: #{data[i]}\n"
+      end
+    
+      lines
+    end
+
+    def Association.deserialize(assoc_s)
+      keys = []
+      values = []
+      assoc_s.split("\n").each do |line|
+        k, v = line.split(":", 2)
+        keys << k.strip
+        values << v.strip
+      end
+  
+      version, handle, secret, issued, lifetime, assoc_type = values
+      raise 'VersionError' if version != @@version
+  
+      secret = OpenID::Util.from_base64(secret)
+      issued = issued.to_i
+      lifetime = lifetime.to_i
+      Association.new(handle, secret, issued, lifetime, assoc_type)
+    end
+
+    def initialize(handle, secret, issued, lifetime, assoc_type)
+      if assoc_type != 'HMAC-SHA1'
+        raise ArgumentError, "HMAC-SHA1 is the only supported assoc_type, got #{assoc_type}"
+      end
+      
+      @handle = handle
+      @secret = secret
+      @issued = issued
+      @lifetime = lifetime
+      @assoc_type = assoc_type
+    end
+
+    def expires_in
+      [0, @issued + @lifetime - Time.now.to_i].max
+    end
+
+    def expired?
+      return expires_in == 0
+    end
+
+    def sign(pairs)
+      kv = ''
+      pairs.each {|k,v| kv << "#{k}:#{v}\n"}
+      return OpenID::Util.hmac_sha1(@secret, kv)
+    end
+
+    def sign_hash(fields, hash, prefix='openid.')
+      pairs = []
+      fields.each { |f| pairs << [f, hash[prefix+f]] }
+      return OpenID::Util.to_base64(sign(pairs))
+    end
+
+    def add_signature(fields, hash, prefix='openid.')
+      sig = sign_hash(fields, hash, prefix)
+      signed = fields.join(',')
+      hash[prefix+'sig'] = sig
+      hash[prefix+'signed'] = signed
+    end
+
+    def ==(other)
+      self.instance_variable_hash == other.instance_variable_hash
+    end
+
+  end
+
+end

Added: incubator/heraldry/libraries/ruby/openid/trunk/lib/openid/consumer.rb
URL: http://svn.apache.org/viewvc/incubator/heraldry/libraries/ruby/openid/trunk/lib/openid/consumer.rb?view=auto&rev=462998
==============================================================================
--- incubator/heraldry/libraries/ruby/openid/trunk/lib/openid/consumer.rb (added)
+++ incubator/heraldry/libraries/ruby/openid/trunk/lib/openid/consumer.rb Wed Oct 11 15:13:01 2006
@@ -0,0 +1,909 @@
+require "uri"
+
+require "openid/util"
+require "openid/dh"
+require "openid/fetchers"
+require "openid/association"
+require "openid/discovery"
+
+
+# Everything in this library exists within the OpenID Module.  Users of
+# the library should look at OpenID::Consumer and/or OpenID::Server
+module OpenID
+
+  # ==Overview
+  #
+  # Brief terminology:
+  #
+  # [+Consumer+]
+  #   The website wanting to verify an OpenID identity URL.  Sometimes
+  #   called a "relying party".  If you want people to log into your site
+  #   using OpenID, then you are the consumer.
+  # 
+  # [+Server+]
+  #   The website which makes assertions as to whether or not the user
+  #   at the end of the browser owns the URL they say they do.
+  #
+  # [+Redirect+]
+  #   An HTTP 302 (Temporarily Moved) redirect.  When issued as an HTTP
+  #   response from the server, the browser changes it's location to the
+  #   value specified.
+  #
+  # The OpenID authentication process requires the following steps,
+  # as visible to the user of this library:
+  #
+  # 1. The user enters their OpenID into a field on the consumer's
+  #    site, and hits a login button.
+  #
+  # 2. The consumer site discovers the user's OpenID server information using
+  #    the Yadis protocol (Potentially falling back to OpenID 1.0 "linkrel"
+  #    discovery).
+  #
+  # 3. The consumer site prepares a URL to be sent to the  server
+  #    which contains the OpenID autentication information, and 
+  #    issues a redirect user's browser.
+  #
+  # 4. The server then verifies that the user owns the URL
+  #    provided, and sends the browser a redirect
+  #    back to the consumer.  This redirect contains the
+  #    server's response to the authentication request.
+  #
+  # The most important part of the flow to note is the consumer's site
+  # must handle two separate HTTP requests in order to perform the
+  # full identity check.  These two HTTP requests are described in
+  # steps 1 and 4 above, and are handled by Consumer.begin and
+  # Consumer.complete respectively.
+  #
+  #
+  # ==Consumer Library Design
+  #
+  # The library is designed with the above flow in mind.  The
+  # goal is to make it as easy as possible to perform the above steps
+  # securely.
+  #
+  # At a high level, there are two important parts in the consumer
+  # library.  The first important part is the OpenID::Consumer class,
+  # which contains the public interface to the consumer logic.
+  # The second is the OpenID::Store class, which defines the
+  # interface needed to store the state the consumer needs to maintain
+  # between requests.
+  #
+  # In general, the second part is less important for users of the
+  # library to know about, as several concrete store implementations are
+  # provided.  The user simply needs to choose the store which best fits
+  # their environment and requirements.
+  #
+  #
+  # ==Stores and Dumb Mode
+  #
+  # OpenID is a protocol that works best when the consumer site is
+  # able to store some state.  This is the normal mode of operation
+  # for the protocol, and is sometimes referred to as smart mode.
+  # There is also a fallback mode, known as dumb mode, which is
+  # available when the consumer site is not able to store state.  This
+  # mode should be avoided when possible, as it leaves the
+  # implementation more vulnerable to replay attacks.
+  #
+  # The mode the library works in for normal operation is determined
+  # by the store that it is given.  The store is an abstraction that
+  # handles the data that the consumer needs to manage between HTTP
+  # requests in order to operate efficiently and securely.
+  #
+  # Several store implementation are provided, and the interface is
+  # fully documented so that custom stores can be used as well. The
+  # implementations that are provided allow the consumer site to store
+  # data in a couple of different ways: in the filesystem,
+  # or in an SQL database.
+  #
+  # There is an additional concrete store provided that puts the
+  # consumer in dumb mode.  This is not recommended, as it removes the
+  # library's ability to stop replay attacks reliably.  It still uses
+  # time-based checking to make replay attacks only possible within a
+  # small window, but they remain possible within that window.  This
+  # store should only be used if the consumer site has no way to
+  # retain data between requests at all. See DumbStore for more info.
+  #
+  # If your ennvironment permits, use of the FilesystemStore
+  # is recommended.
+  #
+  #
+  # ==Immediate Mode
+  #
+  # If you are new to OpenID, it is suggested that you skip this section
+  # and refer to it later.  Immediate mode is an advanced consumer topic.
+  #
+  # In the flow described in the overview, the user may need to confirm to the
+  # identity server that it's ok to authorize his or her identity.
+  # The server may draw pages asking for information from the user
+  # before it redirects the browser back to the consumer's site.  This
+  # is generally transparent to the consumer site, so it is typically
+  # ignored as an implementation detail.
+  #
+  # There can be times, however, where the consumer site wants to get
+  # a response immediately.  When this is the case, the consumer can
+  # put the library in immediate mode.  In immediate mode, there is an
+  # extra response possible from the server, which is essentially the
+  # server reporting that it doesn't have enough information to answer
+  # the question yet.  In addition to saying that, the identity server
+  # provides a URL to which the user can be sent to provide the needed
+  # information and let the server finish handling the original
+  # request.
+  #
+  # You may invoke immediate mode when building the redirect URL to the
+  # OpenID server in the SuccessRequest.redirect_url method.  Pass true
+  # for the +immediate+ paramter.  Read the interface for Consumer.complete
+  # for information about handling the additional response.
+  #
+  # ==Using the Library
+  #
+  # Integrating this library into an application is a
+  # relatively straightforward process.  The process usually follows this plan:
+  #
+  # Add an OpenID login field somewhere on your site.  When an OpenID
+  # is entered in that field and the form is submitted, it should make
+  # a request to the site which includes that OpenID URL.
+  #
+  # When your site receives that request, it should create an
+  # OpenID::Consumer instance, and call
+  # OpenID::Consumer.begin.  If begin completes successfully,
+  # it will return a SuccessRequest object.  Otherwise it will subclass
+  # of OpenIDStatus which contains additional information about the
+  # the failure.
+  #
+  # If successful, build a redirect URL to the server by calling
+  # SuccessRequest.redirect_url and send back an HTTP 302 redirect
+  # of that URL to the user's browser. The redirect_url accepts a 
+  # return_to parameter, which is the URL to which they will return
+  # to fininsh the OpenID transaction.  This URL is supplied by you,
+  # and should be able to handle step 4 of the flow described in the
+  # overview.
+  #
+  # That's the first half of the authentication process.  The second
+  # half of the process is done after the OpenID server sends the
+  # user's browser a redirect back to your site with the
+  # authentication response.
+  #
+  # When that happens, the browser will make a request to the return_to
+  # URL you provided to the SuccessRequest.redirect_url
+  # method.  The request will have several query parameters added
+  # to the URL by the identity server as the information necessary to
+  # finish the request.
+  #
+  # Your job here is to make sure that the action performed at the return_to
+  # URL creates an instnce of OpenID::Consumer, and calls the Consumer.complete
+  # method. This call will
+  # return a SuccessResponse object, or a subclass of OpenIDStatus explaining,
+  # the failure.  See the documentation for Consumer.complete
+  # for a full explanation of the possible responses.
+  #
+  # If you received a SuccessResponse, you may access the identity URL
+  # of the user though it's +identity_url+ method.
+  class Consumer
+    
+    @@service_key = '_openid_consumer_service'
+    @@disco_suffix = 'xopenid_services'
+    attr_accessor :consumer, :session, :fetcher
+
+    # Creates a new OpenID::Consumer instance.  You should create a new
+    # instance of the Consumer object with every HTTP request that handles
+    # OpenID transactions.  Do not store the instance of it in a
+    # global variable somewhere.
+    #
+    # [+session+]
+    #   A hash-like object representing the user's session data.  This is
+    #   used for keeping state of the OpenID transaction when the user is
+    #   redirected to the server.  In a rails application, the controller's
+    #   @session instance variable should be used.
+    #
+    # [+store+] 
+    #   This must be an object that implements the OpenID::Store interface.
+    #   Several concrete implementations are provided, to cover
+    #   most common use cases.  We recommend using the simple file based
+    #   store bundled with the library: OpenID::FilesystemStore.
+    # 
+    # [+fetcher+]
+    #   Optional.  If provided, this must be an instance that implements
+    #   OpenID::Fetcher interface.  If no fetcher is provided,
+    #   an OpenID::StandardFetcher instance will be created
+    #   for you automatically.  If you need custom fetcher behavior, it
+    #   is probably best to subclass StandardFetcher, and pass your instance
+    #   in here.
+    # 
+    # This object keeps an internal instance of OpenID::GenericConsumer
+    # for low level OpenID calls, called +consumer+. You may use a custom    
+    # certificate authority PEM file for veryifying HTTPS server certs
+    # by calling the GenericConsumer.ca_path= method of the +consumer+
+    # instance variable.
+    def initialize(session, store, fetcher=nil)
+      @session = session
+      @consumer = GenericConsumer.new(store, fetcher)
+    end
+
+    # +begin+ is called to start the OpenID verification process.  See steps
+    # 1-2 in the overview at the top of this file.
+    #
+    # ==Parameters
+    #
+    # [+user_url+]
+    #   Identity URL given by the user. +begin+ performs a textual
+    #   transformation of the URL to try and make sure it is "normalized",
+    #   for example, a user_url of example.com will be normalized to
+    #   http://example.com/ normalizing and resolving any redirects
+    #   the server might issue.
+    #
+    # ==Return Value
+    #
+    # +begin+ returns a subclass of OpenIDStatus, which is an object
+    # that has a +status+ method.  The status methodfor this object will either
+    # return OpenID::SUCCESS, or OpenID::FAILURE.  Generally +begin+ will fail
+    # if the users' OpenID page cannot be retrieved or OpenID server
+    # information cannot be determined.
+    # 
+    # ===Success
+    #
+    # In the case that request.status equals OpenID::SUCCESS, the response
+    # will be of type OpenID::SuccessRequest.  The SuccessRequest object
+    # may the be used to add simple registration extension arguments,
+    # using SuccessRequest.add_extension_arg, and build the redirect
+    # url to the server using SuccessRequest.redirect_url as described
+    # in step 3 of the overview.
+    #
+    # The next step in the success case is to actually build the redirect
+    # URL to the server.  Please see the documentation for
+    # SuccessRequest.redirect_url for details.  Once the redirect url
+    # is created, you should issue an HTTP 302 temporary redirect to the
+    # user's browser, sending her to the OpenID server.  Once the user
+    # finishes the operations on the server, she will be redirected back to
+    # the return_to URL you passed to redirect_url, which should invoke
+    # the Consumer.complete method.
+    #
+    # ===Failure
+    # 
+    # If the library is unable to fetch the +user_url+, or no server
+    # information can be determined, or if the server information is malformed,
+    # +begin+ will return a FailureRequest object.  The status method of this
+    # object will return OpenID::FAILURE.  FailureRequest objects have a
+    # +msg+ method which provides more detailed information as to why
+    # the request failed.
+    def begin(user_url)
+      discovery = self.get_discovery(user_url)
+
+      unless discovery
+        return FailureRequest.new("Don't know how to find services for that identifier")
+      end
+
+      service = discovery.next_service
+
+      unless service
+        return FailureRequest.new('No service endpoints found.')
+      end
+      
+      return self.begin_without_discovery(service)
+    end
+
+    # Start the OpenID transaction without doing OpenID server
+    # discovery. This method is used internally by Consumer.begin after
+    # discovery is performed, and exists to provide an interface for library
+    # users needing to perform their own discovery.
+    #
+    # ==Parameters
+    #
+    # +service+ must be an OpenID::OpenIDServiceEnpoint object, or an object
+    # that implements it's interface.  You may produce these objects
+    # and perform discovery manually using OpenID::OpenIDDiscovery.
+    #
+    # ==Return Value
+    # 
+    # +begin_without_discovery+ always returns an OpenID::SuccessRequest
+    # object.  Please see the success documentation for OpenID::Consumer.begin
+    # for more information.
+    def begin_without_discovery(service)
+      request = @consumer.begin(service)
+      @session[@@service_key] = service
+      return request
+    end
+    
+    # Called to interpret the server's response to an OpenID request. It
+    # is called in step 4 of the flow described in the consumer overview.
+    #
+    # ==Parameters
+    # [+query+]
+    #   A hash of the query paramters for this HTTP request.
+    #
+    # ==Return Value
+    # Return value is a subclass of OpenIDStatus, and may have a status
+    # of OpenID::SUCCESS, OpenID::CANCEL, OpenID::FAILURE,
+    # or OpenID::SETUP_NEEDED.  The status may be accessed through the
+    # +status+ method of the response object.
+    #
+    # When OpenID::SUCCESS is returned, the response object will be of 
+    # type SuccessResponse, which has several useful attributes including
+    # +identity_url+, +service+, and a method +extension_response+ for
+    # extracting potential signed extension reponses from the server.  See
+    # the documentation for OpenID::SuccessResponse for more information
+    # about it's interface and methods.
+    #
+    # In the case of response.status being OpenID::CANCEL, the user cancelled
+    # the OpenID transaction on the server. The response will be an instance
+    # of OpenID::CancelResponse, and you may access the originally submitted
+    # identity URL and service information through that object.
+    #
+    # When status is OpenID::FAILURE, the object will be an instance of
+    # OpenID::FailureResponse. If the identity which failed can be determined
+    # it will be available by accessing the +identity_url+ attribute of the
+    # response.  FailureResponse objects also have a +msg+ attribute
+    # which may be useful in debugging.  If no msg is specified, msg will be
+    # nil.
+    #
+    # When OpenID::SETUP_NEEDED is returned, the response object is an 
+    # instance of OpenID::SetupNeededResponse. The useful piece of information
+    # contained in this response is the +setup_url+ method, which
+    # should be used to send the user to the server and log in.
+    # This response is only generated by immediate
+    # mode requests (openid.mode=immediate).  The user should be redirected
+    # in to the +setup_url+, either in the current window or in a 
+    # new browser window.
+    def complete(query)
+      service = @session[@@service_key]
+      
+      if service.nil?
+        resp = FailureResponse.new(nil, 'No session state found.')
+      else
+        resp = @consumer.complete(query, service)
+      end
+
+      disco = self.get_discovery(resp.identity_url)
+
+      if [SUCCESS, CANCEL].member?(resp.status)
+        if resp.identity_url
+          resp.service = disco.finish
+        end
+      else
+        resp.service = disco.current
+      end
+
+      # want to delete service unless status is SETUP_NEEDED,
+      # because we still need the service info when the user returns from
+      # the server
+      unless resp.status == SETUP_NEEDED
+        @session[@@service_key] = nil
+      end
+
+      return resp
+    end
+
+    protected
+    
+    # Used internally to create an instnace of the OpenIDDiscovery object.
+    def get_discovery(url)
+      if XRI::identifier_scheme(url) == :xri
+        XRIDiscovery.new(@session, url, @@disco_suffix)
+      else
+        url = OpenID::Util.normalize_url(url)
+        if url
+          user_url = user_url.to_s
+          OpenIDDiscovery.new(@session, url, @consumer.fetcher, @@disco_suffix)
+        else
+          nil
+        end
+      end
+    end
+  end
+
+  # This class implements the common logic for OpenID consumers.  It
+  # is used by the higher level OpenID::Consumer class.  Only advanced
+  # users with special needs should ever have to look at this class.
+  #
+  # The only part of the library which has to be used and isn't
+  # documented in full here is the store required to create an
+  # OpenID::Consumer instance.  More on the abstract store type and
+  # concrete implementations of it that are provided in the documentation
+  # of OpenID::Consumer.new 
+  class GenericConsumer
+
+    # Number of characters to be used in generated nonces
+    @@NONCE_LEN = 8
+    
+    # Nonce character set
+    @@NONCE_CHRS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
+    @@D_SUFFIX = 'openid_disco'
+
+    attr_reader :fetcher
+
+    public
+        
+    # Creates a new Consumer instance.  You should create a new
+    # instance of the Consumer object with every HTTP request.  Do not
+    # store the instance of it in a global variable somewhere.
+    #
+    # [+store+] 
+    #   This must be an object that implements the OpenID::Store interface.
+    #   Several concrete implementations are provided, to cover
+    #   most common use cases.  We recommend using the simple file based
+    #   store bundled with the library: OpenID::FilesystemStore.
+    # 
+    # [+fetcher+]
+    #   Optional.  If provided, this must be an instance that implements
+    #   Fetcher interface.  If no fetcher is provided,
+    #   an instance of OpenID::StandardFetcher will be created for
+    #   you automatically.
+    def initialize(store, fetcher=nil)
+      if fetcher.nil?
+        fetcher = StandardFetcher.new
+      end
+      @store = store
+      @fetcher = fetcher
+      @ca_path = nil
+    end
+    
+    # Set the path to a pem certificate authority file for verifying
+    # server certificates during HTTPS.  If you are interested in verifying
+    # certs like the mozilla web browser, have a look at the files here:
+    #
+    # http://curl.haxx.se/docs/caextract.html
+    def ca_path=(ca_path)
+      ca_path = ca_path.to_s
+      if File.exists?(ca_path)
+        @ca_path = ca_path
+        @fetcher.ca_path = ca_path
+      else
+        raise ArgumentError, "#{ca_path} is not a valid file path"
+      end
+    end
+
+    # See the interface documentation for Consumer.begin_without_discovery
+    # for arugumnets and return values of this method.
+    # begin_without_discovery is a light wrapper around this method, and the
+    # has the same interface.
+    def begin(service)
+      nonce = self.create_nonce
+      assoc = self.get_association(service.server_url)
+      return SuccessRequest.new(assoc, nonce, service)
+    end
+
+    # Please see the interface docs for Consumer.complete.  This method accpets
+    # a +service+ paramter which is provided though the SuccessRequest object
+    # generated in the +begin+ call.  The +service+ should be stored somewhere
+    # in the user's session or environment and passed into this method
+    # along with the full query string Hash.  Consumer.complete has the
+    # full list of return values for this method.
+    def complete(query, service_endpoint)
+      return FailureResponse.new(nil, msg='no session state found') unless service_endpoint
+
+      consumer_id = service_endpoint.consumer_id
+      server_id = service_endpoint.server_id
+      server_url = service_endpoint.server_url
+
+      # get the nonce out of the query
+      nonce = query['nonce']
+      if nonce.nil?
+        return FailureResponse.new(consumer_id, 'could not extract nonce')
+      end
+
+      mode = query["openid.mode"]
+
+      case mode
+      when "cancel"        
+        return CancelResponse.new(consumer_id)
+
+      when "error"
+        error = query["openid.error"]
+        unless error.nil?
+          OpenID::Util.log('Error: '+error)
+        end
+        return FailureResponse.new(nil, msg=error)
+
+      when "id_res"        
+        return self.do_id_res(nonce, consumer_id, server_id, server_url, query)
+
+      else
+        return FailureResponse.new(nil, msg="unknown mode #{mode}")
+      end
+
+    end
+
+    # Low level method for handling the OpenID server response.
+    def do_id_res(nonce, consumer_id, server_id, server_url, query)
+      user_setup_url = query["openid.user_setup_url"]
+      if user_setup_url
+        return SetupNeededResponse.new(user_setup_url)
+      end
+      
+      return_to = query["openid.return_to"]
+      server_id2 = query["openid.identity"]
+      assoc_handle = query["openid.assoc_handle"]
+      
+      if return_to.nil?
+        return FailureResponse.new(consumer_id, msg='openid.return_to was nil')
+      elsif server_id2.nil?
+        return FailureResponse.new(consumer_id, msg='openid.identity was nil')
+      elsif assoc_handle.nil?
+        return FailureResponse.new(consumer_id, msg='openid.assoc_handle was nil')
+      end
+      
+      if server_id != server_id2
+        return FailureResponse.new(consumer_id, msg='server ids do not match')
+      end
+      
+      assoc = @store.get_association(server_url, assoc_handle)
+    
+      if assoc.nil?
+        # It's not an association we know about. Dumb mode is our
+        # only possible path for recovery.
+        code, msg = self.check_auth(nonce, query, server_url)
+        if code == SUCCESS
+          return SuccessResponse.new(consumer_id, query)
+        else
+          return FailureResponse.new(consumer_id, 'check_auth failed: #{msg}')
+        end
+      end
+
+      if assoc.expires_in <= 0
+        OpenID::Util.log("Association with #{server_url} expired")
+        return FailureResponse.new(consumer_id, 'assoc expired')
+      end
+
+      # Check the signature
+      sig = query["openid.sig"]
+      return FailureResponse.new(consumer_id, 'no sig') if sig.nil?
+      signed = query["openid.signed"]
+      return FailureResponse.new(consumer_id, 'no signed') if signed.nil?
+      
+      args = OpenID::Util.get_openid_params(query)
+      signed_list = signed.split(",")
+      _signed, v_sig = OpenID::Util.sign_reply(args, assoc.secret, signed_list)
+
+      if v_sig != sig
+        return FailureResponse.new(consumer_id, 'sig mismatch')
+      end
+
+      unless @store.use_nonce(nonce)
+        return FailureResponse.new(consumer_id, 'nonce already used')
+      end
+
+      return SuccessResponse.new(consumer_id, query)
+    end
+
+    # Low level method for performing OpenID check_authenticaion requests
+    def check_auth(nonce, query, server_url)
+      check_args = OpenID::Util.get_openid_params(query)
+      check_args["openid.mode"] = "check_authentication"
+      post_data = OpenID::Util.urlencode(check_args)
+
+      ret = @fetcher.post(server_url, post_data)
+      if ret.nil?
+        return FAILURE, "unable to post to #{server_url}"
+      else
+        url, body = ret
+      end
+    
+      results = OpenID::Util.parsekv(body)
+      is_valid = results.fetch("is_valid", "false")
+    
+      if is_valid == "true"
+
+        # we started this request with a bad association,
+        # falling back to dumb mode, the invalidate_handle tells
+        # us to handle of the assoc to remove from our store.
+        invalidate_handle = results["invalidate_handle"]
+        if invalidate_handle
+          @store.remove_association(server_url, invalidate_handle)
+        end
+
+        # make sure response is not getting replayed by checking the nonce
+        unless @store.use_nonce(nonce)
+          return FAILURE, "#{server_url}, nonce #{nonce} already used"
+        end
+
+        # is_valid = true, and we successfully used the nonce.
+        return SUCCESS, nil
+      end
+    
+      error = results["error"]
+      if error
+        msg = "error from server: #{error}"
+      else
+        msg = "is_valid was false"
+      end
+      return FAILURE, msg
+    end
+
+    # Create a nonce and store it for preventing replace attacks.
+    def create_nonce
+      # build the nonce and store it
+      nonce = OpenID::Util.random_string(@@NONCE_LEN, @@NONCE_CHRS)
+      @store.store_nonce(nonce)
+      return nonce
+    end
+
+    # Get existing or create a new association (shared secret) with the
+    # server at +server_url+.
+    def get_association(server_url)
+      return nil if @store.dumb?
+      assoc = @store.get_association(server_url)
+      return assoc unless assoc.nil?
+      return self.associate(server_url)    
+    end
+    
+    # Make the openid.associate call to the server.
+    def associate(server_url)
+      dh = OpenID::DiffieHellman.new
+      cpub = OpenID::Util.to_base64(OpenID::Util.num_to_str(dh.public))
+      args = {
+        'openid.mode' => 'associate',
+        'openid.assoc_type' =>'HMAC-SHA1',
+        'openid.session_type' =>'DH-SHA1',
+        'openid.dh_modulus' => OpenID::Util.to_base64(OpenID::Util.num_to_str(dh.p)),
+        'openid.dh_gen' => OpenID::Util.to_base64(OpenID::Util.num_to_str(dh.g)),
+        'openid.dh_consumer_public' => cpub
+      }
+      body = OpenID::Util.urlencode(args)
+      
+      ret = @fetcher.post(server_url, body)
+      return nil if ret.nil?
+      url, data = ret
+      results = OpenID::Util.parsekv(data)
+      
+      assoc_type = results["assoc_type"]
+      return nil if assoc_type.nil? or assoc_type != "HMAC-SHA1"
+      
+      assoc_handle = results["assoc_handle"]
+      return nil if assoc_handle.nil?    
+      
+      expires_in = results.fetch("expires_in", "0").to_i
+
+      session_type = results["session_type"]
+      if session_type.nil?
+        secret = OpenID::Util.from_base64(results["mac_key"])
+      else
+        return nil if session_type != "DH-SHA1"
+        
+        dh_server_public = results["dh_server_public"]
+        return nil if dh_server_public.nil?
+        
+        spub = OpenID::Util.str_to_num(OpenID::Util.from_base64(dh_server_public))
+        dh_shared = dh.get_shared_secret(spub)
+        enc_mac_key = results["enc_mac_key"]
+        secret = OpenID::Util.strxor(OpenID::Util.from_base64(enc_mac_key),
+                                     OpenID::Util.sha1(OpenID::Util.num_to_str(dh_shared)))
+      end
+   
+      assoc = OpenID::Association.from_expires_in(expires_in, assoc_handle,
+                                                  secret, 'HMAC-SHA1')
+      @store.store_association(server_url, assoc)
+      return assoc
+    end
+
+  end
+
+  # Base class for objects returned from Consumer.begin and Consumer.complete
+  class OpenIDStatus
+
+    attr_reader :status
+
+    def initialize(status)
+      @status = status
+    end
+
+  end
+
+  # Returned by Consumer.begin when server information cannot be determined
+  # from the provided identity URL.  The +msg+ method may return a useful
+  # string for debugging the request.
+  class FailureRequest < OpenIDStatus
+
+    attr_reader :msg
+
+    def initialize(msg='')
+      super(FAILURE)
+      @msg = msg
+    end
+
+  end
+
+  # Encapsulates the information the library retrieves and uses during
+  # Consumer.begin.
+  class SuccessRequest < OpenIDStatus
+    
+    attr_reader :server_id, :server_url, :nonce, :identity_url, \
+                :service, :return_to_args
+    
+    # Creates a new SuccessRequest object.  This just stores each
+    # argument in an appropriately named field.
+    #
+    # Users of this library should not create instances of this
+    # class.  Instances of this class are created by Consumer
+    # during begin.
+    def initialize(assoc, nonce, service)
+      super(SUCCESS)
+      @service = service
+      @server_id = service.server_id
+      @server_url = service.server_url
+      @identity_url = service.consumer_id
+      @extra_args = {}
+      @return_to_args = {'nonce' => nonce}
+
+      @assoc = assoc
+      @nonce = nonce
+    end
+
+    # Called to construct the redirect URL sent to
+    # the browser to ask the server to verify its identity.  This is
+    # called in step 3 of the flow described in the overview.
+    # Please note that you don't need to call this method directly
+    # unless you need to create a custom redirect, as it is called
+    # directly during begin. The generated redirect should be
+    # sent to the browser which initiated the authorization request.
+    #
+    # ==Parameters
+    # [+trust_root+]
+    #   This is a URL that will be sent to the
+    #   server to identify this site.  The OpenID spec (
+    #   http://www.openid.net/specs.bml#mode-checkid_immediate )
+    #   has more information on what the trust_root value is for
+    #   and what its form can be.  While the trust root is
+    #   officially optional in the OpenID specification, this
+    #   implementation requires that it be set.  Nothing is
+    #   actually gained by leaving out the trust root, as you can
+    #   get identical behavior by specifying the return_to URL as
+    #   the trust root.
+    #
+    # [+return_to+]
+    #   This is the URL that will be included in the
+    #   generated redirect as the URL the OpenID server will send
+    #   its response to.  The URL passed in must handle OpenID
+    #   authentication responses.
+    #
+    # [+immediate+]
+    #   Optional.  If +immediate+ is true, the request will be made using
+    #   openid.mode=checkid_immediate instead of the standard
+    #   openid.mode=checkid_setup. 
+    #
+    # ==Return Value
+    # Return a string which is the URL to which you should redirect the user.
+    def redirect_url(trust_root, return_to, immediate=false)
+      # add the nonce into the return_to url
+      return_to = OpenID::Util.append_args(return_to, @return_to_args)
+
+      redir_args = {
+        "openid.identity" => @server_id,
+        "openid.return_to" => return_to,
+        "openid.trust_root" => trust_root,
+        "openid.mode" => immediate ? 'checkid_immediate' : 'checkid_setup'
+      }
+
+      redir_args["openid.assoc_handle"] = @assoc.handle if @assoc
+      redir_args.update(@extra_args)
+     
+      return OpenID::Util.append_args(server_url, redir_args).to_s
+    end
+
+    # get the return_to URL
+    def return_to(return_to)
+      OpenID::Util.append_args(return_to, @return_to_args)
+    end
+
+    # Add an openid extension argument to the request. A simple resitration
+    # request may look something like:
+    #
+    #  req.add_extension_arg('sreg','required','email')
+    #  req.add_extension_arg('sreg','optional','nickname,gender')
+    #  req.add_extension_arg('sreg','policy_url','http://example.com/policy')
+    def add_extension_arg(namespace, key, value)
+      @extra_args['openid.'+namespace+'.'+key] = value
+    end
+
+    def add_arg(key, value)
+      @extra_args[key] = value
+    end
+
+    # Checks to see if the user's OpenID server additionally supports
+    # the extensions service type url provided.
+    def uses_extension?(extension_url)
+      return false unless extension_url
+
+      @service.service_types.each do |url|
+        if OpenID::Util.urls_equal?(url, extension_url)
+          return true
+        end
+      end
+
+      return false
+    end
+
+  end
+
+  # Encapsulates the information that is useful after a successful
+  # Consumer.complete call.  Verified identity URL and
+  # signed extension response values are available through this object.
+  class SuccessResponse < OpenIDStatus
+    
+    attr_reader :identity_url
+    attr_accessor :service
+
+    # Instances of this object will be created for you automatically
+    # by OpenID::Consumer.  You should never have to construct this
+    # object yourself.
+    def initialize(identity_url, query)
+      super(SUCCESS)
+      @identity_url = identity_url
+      @query = query
+      @service = nil
+    end
+
+    # Returns all the arguments from an extension's namespace.  For example
+    # 
+    #   response.extension_response('sreg')
+    # 
+    # may return something like:
+    #
+    #  {'email' => 'mayor@example.com', 'nickname' => 'MayorMcCheese'}
+    #
+    # The extension namespace is not included in the keys of the returned
+    # hash.  Values returned from this method are guaranteed to be signed.
+    # Calling this method should be the *only* way you access extension
+    # response data!
+    def extension_response(extension_name)      
+      prefix = extension_name
+      
+      signed = @query['openid.signed']
+      return nil if signed.nil?
+      
+      signed = signed.split(',')
+      extension_args = {}
+      extension_prefix = prefix + '.'
+      
+      signed.each do |arg|
+        if arg.index(extension_prefix) == 0
+          query_key = 'openid.'+arg
+          extension_args[arg[(1+prefix.length..-1)]] = @query[query_key]
+        end
+      end
+      
+      return extension_args
+    end
+
+  end
+
+  # Object returned from Consumer.complete when the auth request failed.
+  # The +identity_url+, +msg+, and +service+ methods may contain useful
+  # information about the failure if it is available.  These methods will
+  # return nil if no useful info can be determined.
+  class FailureResponse < OpenIDStatus
+    
+    attr_accessor :identity_url, :msg, :service
+
+    def initialize(identity_url=nil, msg=nil)
+      super(FAILURE)
+      @identity_url = identity_url
+      @msg = msg
+    end
+
+  end
+  
+  # Returned by Consumer.begin in immediate mode when the user needs to
+  # log into the OpenID server.  User should be redirected to the value
+  # returned from the +setup_url+ method.
+  class SetupNeededResponse < OpenIDStatus
+    
+    attr_reader :setup_url
+    attr_accessor :identity_url, :service
+
+    def initialize(setup_url)
+      super(SETUP_NEEDED)
+      @setup_url = setup_url
+    end
+
+  end
+
+  # Response returned from Consumer.complete when the user cancels the
+  # OpenID transaction.
+  class CancelResponse < OpenIDStatus
+    attr_accessor :identity_url, :service
+    def initialize(identity_url)
+      super(CANCEL)
+      @identity_url = identity_url
+    end
+  end
+
+end

Added: incubator/heraldry/libraries/ruby/openid/trunk/lib/openid/dh.rb
URL: http://svn.apache.org/viewvc/incubator/heraldry/libraries/ruby/openid/trunk/lib/openid/dh.rb?view=auto&rev=462998
==============================================================================
--- incubator/heraldry/libraries/ruby/openid/trunk/lib/openid/dh.rb (added)
+++ incubator/heraldry/libraries/ruby/openid/trunk/lib/openid/dh.rb Wed Oct 11 15:13:01 2006
@@ -0,0 +1,48 @@
+require "openid/util"
+
+module OpenID
+
+  # Encapsulates a Diffie-Hellman key exchange.  This class is used
+  # internally by both the consumer and server objects.
+  #
+  # Read more about Diffie-Hellman on wikipedia:
+  # http://en.wikipedia.org/wiki/Diffie-Hellman
+  class DiffieHellman
+
+    @@DEFAULT_MOD = 155172898181473697471232257763715539915724801966915404479707795314057629378541917580651227423698188993727816152646631438561595825688188889951272158842675419950341258706556549803580104870537681476726513255747040765857479291291572334510643245094715007229621094194349783925984760375594985848253359305585439638443
+    @@DEFAULT_GEN = 2
+
+    attr_reader :p, :g, :public
+    
+    def DiffieHellman.from_base64(p=nil, g=nil)
+      unless p.nil?
+        p = OpenID::Util.base64_to_num(p)
+      end
+      unless g.nil?
+        g = OpenID::Util.base64_to_num(g)
+      end
+      DiffieHellman.new(p, g)
+    end
+
+    def initialize(p=nil, g=nil)
+      @p = p.nil? ? @@DEFAULT_MOD : p
+      @g = g.nil? ? @@DEFAULT_GEN : g
+      
+      @private = OpenID::Util.rand(@p-2) + 1
+      @public = OpenID::Util.powermod(@g, @private, @p)
+    end
+
+    def get_shared_secret(composite)
+      OpenID::Util.powermod(composite, @private, @p)
+    end
+
+    def xor_secrect(composite, secret)
+      dh_shared = get_shared_secret(composite)
+      sha1_dh_shared = OpenID::Util.sha1( \
+                          OpenID::Util.num_to_str(dh_shared))
+      return OpenID::Util.strxor(secret, sha1_dh_shared)
+    end
+
+  end
+
+end

Added: incubator/heraldry/libraries/ruby/openid/trunk/lib/openid/discovery.rb
URL: http://svn.apache.org/viewvc/incubator/heraldry/libraries/ruby/openid/trunk/lib/openid/discovery.rb?view=auto&rev=462998
==============================================================================
--- incubator/heraldry/libraries/ruby/openid/trunk/lib/openid/discovery.rb (added)
+++ incubator/heraldry/libraries/ruby/openid/trunk/lib/openid/discovery.rb Wed Oct 11 15:13:01 2006
@@ -0,0 +1,122 @@
+require "openid/util"
+require "openid/service"
+require "openid/parse"
+
+# try and use the yadis gem, falling back to system yadis
+begin
+  require 'rubygems'
+  require_gem 'ruby-yadis', ">=0.3.3"  
+rescue LoadError
+  require "yadis"
+end
+
+module OpenID
+
+  OPENID_IDP_2_0_TYPE = 'http://openid.net/server/2.0'
+  OPENID_2_0_TYPE = 'http://openid.net/signon/2.0'
+  OPENID_1_2_TYPE = 'http://openid.net/signon/1.2'
+  OPENID_1_1_TYPE = 'http://openid.net/signon/1.1'
+  OPENID_1_0_TYPE = 'http://openid.net/signon/1.0'
+  OPENID_TYPE_URIS = [OPENID_2_0_TYPE,OPENID_1_2_TYPE,
+                     OPENID_1_1_TYPE,OPENID_1_0_TYPE]
+
+  # OpenID::Discovery encapsulates the logic for doing Yadis and OpenID 1.0
+  # style server discovery.  This class uses a session object to manage 
+  # a list of tried OpenID servers for implemeting server fallback.  This is
+  # useful the case when a user's primary server(s) is not available, and
+  # will allow then to try again with one of their alternates.
+  class OpenIDDiscovery < Discovery
+
+    def initialize(session, url, fetcher, suffix=nil)
+      super(session, url, suffix)
+      @fetcher = fetcher
+    end
+
+    # Pass in a custom filter here if you like.  Otherwise you'll get all
+    # OpenID sso services. filter should produce objects or subclasses of
+    # OpenIDServiceEndpoint.
+    def discover(filter=nil)
+      unless filter
+        filter = lambda {|s| OpenIDServiceEndpoint.from_endpoint(s)}
+      end
+      
+      begin
+        # do yadis discover, filtering out OpenID services
+        return super(filter)
+      rescue YADISParseError, YADISHTTPError
+
+        # Couldn't do Yadis discovery, fall back on OpenID 1.0 disco
+        status, service = self.openid_discovery(@url)
+        if status == SUCCESS
+          return [service.consumer_id, [service]]
+        end
+      end
+
+      return [nil, []]
+    end
+
+    # Perform OpenID 1.0 style link rel discovery.  No string normalization
+    # will be done on +url+.  See Util.normalize_url for information on
+    # textual URL transformations.
+    def openid_discovery(url)
+      ret = @fetcher.get(url)
+      return [HTTP_FAILURE, nil] if ret.nil?
+      
+      consumer_id, data = ret
+      server = nil
+      delegate = nil
+      parse_link_attrs(data) do |attrs|
+        rel = attrs["rel"]
+        if rel == "openid.server" and server.nil?
+          href = attrs["href"]
+          server = href unless href.nil?
+        end
+        
+        if rel == "openid.delegate" and delegate.nil?
+          href = attrs["href"]
+          delegate = href unless href.nil?
+        end
+      end
+
+      return [PARSE_ERROR, nil] if server.nil?
+    
+      server_id = delegate.nil? ? consumer_id : delegate
+
+      consumer_id = OpenID::Util.normalize_url(consumer_id)
+      server_id = OpenID::Util.normalize_url(server_id)
+      server_url = OpenID::Util.normalize_url(server)
+                  
+      service = OpenID::FakeOpenIDServiceEndpoint.new(consumer_id,
+                                                      server_id,
+                                                      server_url)
+      return [SUCCESS, service]
+    end    
+
+  end
+
+  class XRIDiscovery < Discovery
+    def initialize(session, iname, suffix=nil)
+      super(session, iname, suffix)
+    end
+
+    def discover(filter=nil)
+      begin
+        services = XRI::ProxyResolver.new.query(@url, OPENID_TYPE_URIS)
+      rescue XRI::XRIHTTPError, ArgumentError
+        return [nil, []]
+      end
+      endpoints = []
+      services.each {|s|
+        se = OpenIDServiceEndpoint.from_endpoint(s)
+        if se
+          se.delegate_url = @url
+          se.yadis_url = @url
+          endpoints << se
+        end
+      }
+      return [@url, endpoints]      
+    end
+
+  end
+
+end

Added: incubator/heraldry/libraries/ruby/openid/trunk/lib/openid/fetchers.rb
URL: http://svn.apache.org/viewvc/incubator/heraldry/libraries/ruby/openid/trunk/lib/openid/fetchers.rb?view=auto&rev=462998
==============================================================================
--- incubator/heraldry/libraries/ruby/openid/trunk/lib/openid/fetchers.rb (added)
+++ incubator/heraldry/libraries/ruby/openid/trunk/lib/openid/fetchers.rb Wed Oct 11 15:13:01 2006
@@ -0,0 +1,160 @@
+require "uri"
+require "openid/util"
+
+# Try to use net/https, falling back to no SSL support if it is not available
+begin
+  require 'net/https'
+rescue LoadError
+  OpenID::Util.log('WARNING: no SSL support found.  Will not be able to fetch HTTPS URLs!')
+  HAS_OPENSSL = false
+  require 'net/http'  
+else
+  HAS_OPENSSL = true
+end
+
+# Not all versions of Ruby 1.8.4 have the version of post_connection_check
+# that properly handles wildcard hostnames.  This version of
+# post_connection_check is copied from post April 2006 release of 1.8.4.
+module Net
+  class HTTP
+    def post_connection_check(hostname)
+      check_common_name = true
+      cert = @socket.io.peer_cert
+      cert.extensions.each { |ext|
+        next if ext.oid != "subjectAltName"
+        ext.value.split(/,\s+/).each{ |general_name|
+          if /\ADNS:(.*)/ =~ general_name
+            check_common_name = false
+            reg = Regexp.escape($1).gsub(/\\\*/, "[^.]+")
+            return true if /\A#{reg}\z/i =~ hostname
+          elsif /\AIP Address:(.*)/ =~ general_name
+            check_common_name = false
+            return true if $1 == hostname
+          end
+        }
+      }
+      if check_common_name
+        cert.subject.to_a.each{ |oid, value|
+          if oid == "CN"
+            reg = Regexp.escape(value).gsub(/\\\*/, "[^.]+")
+            return true if /\A#{reg}\z/i =~ hostname
+          end
+        }
+      end
+      raise OpenSSL::SSL::SSLError, "hostname does not match"
+    end
+  end
+end
+
+module OpenID
+
+  # Base Object used by consumer to send http messages
+  class Fetcher
+
+    # Fetch the content of url, following redirects, and return the
+    # final url and page data.  Return nil on failure.    
+    def get(url)
+      raise NotImplementedError
+    end
+    
+    # Post the body string to url. Return the resulting url and page data.
+    # Return nil on failure    
+    def post(url, body)
+      raise NotImplementedError
+    end
+    
+  end
+  
+  # Implemetation of OpenID::Fetcher that uses ruby's Net::HTTP
+  class StandardFetcher < Fetcher
+    
+    attr_accessor :ca_path
+
+    def initialize(read_timeout=20, open_timeout=20)
+      @read_timeout = read_timeout
+      @open_timeout = open_timeout
+      @ca_path = nil
+    end
+    
+    def get(url)    
+      resp, final_url = do_get(url)
+      if resp.nil?
+        nil
+      else
+        [final_url, resp.body]
+      end
+    end
+  
+    def post(url, body)
+      begin
+        uri = URI.parse(url)
+        http = get_http_obj(uri)
+        resp = http.post(uri.request_uri, body,
+                         {"Content-type"=>"application/x-www-form-urlencoded"})
+      rescue
+        nil
+      else
+        [uri.to_s, resp.body]
+      end
+    end
+
+    protected
+    
+    # return a Net::HTTP object ready for use    
+    def get_http_obj(uri)
+      http = Net::HTTP.new(uri.host, uri.port)
+      http.read_timeout = @read_timeout
+      http.open_timeout = @open_timeout
+
+      if uri.scheme == 'https'
+
+        if HAS_OPENSSL
+          http.use_ssl = true
+
+          if @ca_path
+            http.verify_mode = OpenSSL::SSL::VERIFY_PEER
+            http.ca_file = @ca_path
+          else
+            OpenID::Util.log('WARNING: making https request without verifying server certificate.')
+            http.verify_mode = OpenSSL::SSL::VERIFY_NONE
+          end
+          
+        end
+
+      end
+
+      return http
+    end
+    
+    # do a GET following redirects limit deep
+    
+    def do_get(url, limit=5)
+      if limit == 0
+        return nil
+      end
+      begin
+        u = URI.parse(url)
+        http = get_http_obj(u)
+        http.start {
+          if HAS_OPENSSL and u.is_a?(URI::HTTPS) and @ca_path
+            # do the post_connection_check, which verifies that
+            # the host matches the cert
+            http.post_connection_check(u.host)
+          end
+        }
+        resp = http.get(u.request_uri)
+      rescue
+        nil
+      else
+        case resp
+        when Net::HTTPSuccess then [resp, URI.parse(url).to_s]
+        when Net::HTTPRedirection then do_get(resp["location"], limit-1)
+        else
+          nil
+        end
+      end
+    end
+    
+  end
+  
+end

Added: incubator/heraldry/libraries/ruby/openid/trunk/lib/openid/filestore.rb
URL: http://svn.apache.org/viewvc/incubator/heraldry/libraries/ruby/openid/trunk/lib/openid/filestore.rb?view=auto&rev=462998
==============================================================================
--- incubator/heraldry/libraries/ruby/openid/trunk/lib/openid/filestore.rb (added)
+++ incubator/heraldry/libraries/ruby/openid/trunk/lib/openid/filestore.rb Wed Oct 11 15:13:01 2006
@@ -0,0 +1,315 @@
+require 'fileutils'
+require 'pathname'
+require 'tempfile'
+
+require 'openid/util'
+require 'openid/stores'
+require 'openid/association'
+
+module OpenID
+
+  # Filesystem-based store for OpenID associations and nonces.
+  #
+  # Methods of this object may raise SystemCallError if filestystem
+  # related errors are encountered.
+  class FilesystemStore < Store
+  
+    @@FILENAME_ALLOWED = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ.-".split("")
+  
+    # Create a FilesystemStore 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')
+      @auth_key_name = p_dir.join('auth_key')
+      @max_nonce_age = 6 * 60 * 60
+      
+      self.ensure_dir(@nonce_dir)
+      self.ensure_dir(@association_dir)
+      self.ensure_dir(@temp_dir)
+      self.ensure_dir(File.dirname(@auth_key_name))
+    end
+
+    # Read the auth key from the auth key file. Returns nil if there
+    # is currently no auth key.
+    def read_auth_key
+      f = nil
+      begin
+        f = File.open(@auth_key_name)      
+      rescue Errno::ENOENT
+        return nil
+      else
+        return f.read
+      ensure
+        f.close unless f.nil?      
+      end
+    end
+
+    # Generate a new random auth key and safely store it in the location
+    # specified by @auth_key_name
+    def create_auth_key
+      auth_key = OpenID::Util.random_string(@@AUTH_KEY_LEN)
+      f, tmp = mktemp
+      begin
+        begin
+          f.write(auth_key)
+          f.fsync
+        ensure
+          f.close
+        end
+        begin
+          begin
+            File.link(tmp, @auth_key_name)
+          rescue NotImplementedError # no link under windows
+            File.rename(tmp, @auth_key_name)
+          end
+        rescue Errno::EEXIST
+          raise if read_auth_key.nil?
+        end      
+      ensure
+        self.remove_if_present(tmp)
+      end
+
+      auth_key
+    end
+    
+    # Retrieve the auth key from the file specified by
+    # @auth_key_file, creating it if it does not exist
+    def get_auth_key
+      auth_key = read_auth_key
+      if auth_key.nil?
+        auth_key = create_auth_key
+      end
+      
+      if auth_key.length != @@AUTH_KEY_LEN
+        raise StandardError.new("Bad auth key - wrong length")
+      end
+      
+      auth_key
+    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)      
+      filename = self.filename_from_url(server_url)
+      filename += '-' + safe64(handle)
+      @association_dir.join(filename)
+    end
+
+    # Store an association in the assoc directory
+    def store_association(server_url, association)
+      assoc_s = OpenID::Association.serialize(association)
+      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)
+      unless handle.nil?
+        filename = get_association_filename(server_url, handle)
+        return _get_association(filename)
+      end
+      
+      # search though existing files looking for a match
+      prefix = filename_from_url(server_url)
+      assoc_filenames = Dir.entries(@association_dir)
+      assoc_filenames = assoc_filenames.find_all { |f| f.index(prefix) == 0 }
+      
+      assocs = assoc_filenames.collect do |f|
+        _get_association(@association_dir.join(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 = OpenID::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
+
+    # Mark this nonce as present    
+    def store_nonce(nonce)
+      filename = @nonce_dir.join(nonce)
+      File.open(filename, "w").close
+    end
+
+    # Return whether this nonce is present.  As a side-effect, mark it 
+    # as no longer present.
+    def use_nonce(nonce)
+      filename = @nonce_dir.join(nonce)
+      begin
+        st = File.stat(filename)
+      rescue Errno::ENOENT
+        return false
+      else
+        begin
+          File.unlink(filename)
+        rescue Errno::ENOENT
+          return false
+        end      
+        nonce_age = Time.now.to_f - st.mtime.to_f
+        nonce_age <= @max_nonce_age
+      end
+    end
+
+    # Garbage collection routine.  Clean up old associations and nonces.
+    def clean
+      nonces = Dir[@nonce_dir.join("*")]
+      now = Time.now
+      
+      nonces.each do |nonce|
+        filename = nonce_dir.join(nonce)
+        begin
+          st = File.stat(filename)
+        rescue Errno::ENOENT
+          next
+        else
+          nonce_age = now - st.mtime
+          self.remove_if_present(filename) if nonce_age > @max_nonce_age
+        end
+      end
+
+      association_filenames = Dir[@association_dir.join("*")]
+      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 "VersionError"
+            self.remove_if_present(af)
+            next
+          else
+            self.remove_if_present(af) if association.expires_in == 0          
+          end
+        end
+      end
+    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_from_url(url)
+      filename = []
+      url.sub('://','-').split("").each do |c|
+        if @@FILENAME_ALLOWED.index(c)
+          filename << c
+        else
+          filename << sprintf("_%02X", c[0])
+        end    
+      end
+      filename.join("")
+    end
+
+    def safe64(s)
+      s = OpenID::Util.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
+
+
+

Added: incubator/heraldry/libraries/ruby/openid/trunk/lib/openid/htmltokenizer.rb
URL: http://svn.apache.org/viewvc/incubator/heraldry/libraries/ruby/openid/trunk/lib/openid/htmltokenizer.rb?view=auto&rev=462998
==============================================================================
--- incubator/heraldry/libraries/ruby/openid/trunk/lib/openid/htmltokenizer.rb (added)
+++ incubator/heraldry/libraries/ruby/openid/trunk/lib/openid/htmltokenizer.rb Wed Oct 11 15:13:01 2006
@@ -0,0 +1,355 @@
+# = HTMLTokenizer
+#
+# Author::    Ben Giddings  (mailto:bg-rubyforge@infofiend.com)
+# Copyright:: Copyright (c) 2004 Ben Giddings
+# License::   Distributes under the same terms as Ruby
+#
+#
+# This is a partial port of the functionality behind Perl's TokeParser
+# Provided a page it progressively returns tokens from that page
+#
+# $Id: htmltokenizer.rb,v 1.7 2005/06/07 21:05:53 merc Exp $
+
+#
+# A class to tokenize HTML.
+#
+# Example:
+#
+#   page = "<HTML>
+#   <HEAD>
+#   <TITLE>This is the title</TITLE>
+#   </HEAD>
+#    <!-- Here comes the <a href=\"missing.link\">blah</a>
+#    comment body
+#     -->
+#    <BODY>
+#      <H1>This is the header</H1>
+#      <P>
+#        This is the paragraph, it contains
+#        <a href=\"link.html\">links</a>,
+#        <img src=\"blah.gif\" optional alt='images
+#        are
+#        really cool'>.  Ok, here is some more text and
+#        <A href=\"http://another.link.com/\" target=\"_blank\">another link</A>.
+#      </P>
+#    </body>
+#    </HTML>
+#    "
+#    toke = HTMLTokenizer.new(page)
+#
+#    assert("<h1>" == toke.getTag("h1", "h2", "h3").to_s.downcase)
+#    assert(HTMLTag.new("<a href=\"link.html\">") == toke.getTag("IMG", "A"))
+#    assert("links" == toke.getTrimmedText)
+#    assert(toke.getTag("IMG", "A").attr_hash['optional'])
+#    assert("_blank" == toke.getTag("IMG", "A").attr_hash['target'])
+#
+class HTMLTokenizer
+  @@version = 1.0
+
+  # Get version of HTMLTokenizer lib
+  def self.version
+    @@version
+  end
+
+  attr_reader :page
+
+  # Create a new tokenizer, based on the content, used as a string.
+  def initialize(content)
+    @page = content.to_s
+    @cur_pos = 0
+  end
+
+  # Reset the parser, setting the current position back at the stop
+  def reset
+    @cur_pos = 0
+  end
+
+  # Look at the next token, but don't actually grab it
+  def peekNextToken
+    if @cur_pos == @page.length then return nil end
+
+    if ?< == @page[@cur_pos]
+      # Next token is a tag of some kind
+      if '!--' == @page[(@cur_pos + 1), 3]
+        # Token is a comment
+        tag_end = @page.index('-->', (@cur_pos + 1))
+        if tag_end.nil?
+          raise "No end found to started comment:\n#{@page[@cur_pos,80]}"
+        end
+        # p @page[@cur_pos .. (tag_end+2)]
+        HTMLComment.new(@page[@cur_pos .. (tag_end + 2)])
+      else
+        # Token is a html tag
+        tag_end = @page.index('>', (@cur_pos + 1))
+        if tag_end.nil?
+          raise "No end found to started tag:\n#{@page[@cur_pos,80]}"
+        end
+        # p @page[@cur_pos .. tag_end]
+        HTMLTag.new(@page[@cur_pos .. tag_end])
+      end
+    else
+      # Next token is text
+      text_end = @page.index('<', @cur_pos)
+      text_end = text_end.nil? ? -1 : (text_end - 1)
+      # p @page[@cur_pos .. text_end]
+      HTMLText.new(@page[@cur_pos .. text_end])
+    end
+  end
+
+  # Get the next token, returns an instance of
+  # * HTMLText
+  # * HTMLToken
+  # * HTMLTag
+  def getNextToken
+    token = peekNextToken
+    if token
+      # @page = @page[token.raw.length .. -1]
+      # @page.slice!(0, token.raw.length)
+      @cur_pos += token.raw.length
+    end
+    #p token
+    #print token.raw
+    return token
+  end
+
+  # Get a tag from the specified set of desired tags.
+  # For example:
+  # <tt>foo =  toke.getTag("h1", "h2", "h3")</tt>
+  # Will return the next header tag encountered.
+  def getTag(*sought_tags)
+    sought_tags.collect! {|elm| elm.downcase}
+
+    while (tag = getNextToken)
+      if tag.kind_of?(HTMLTag) and
+          (0 == sought_tags.length or sought_tags.include?(tag.tag_name))
+        break
+      end
+    end
+    tag
+  end
+
+  # Get all the text between the current position and the next tag
+  # (if specified) or a specific later tag
+  def getText(until_tag = nil)
+    if until_tag.nil?
+      if ?< == @page[@cur_pos]
+        # Next token is a tag, not text
+        ""
+      else
+        # Next token is text
+        getNextToken.text
+      end
+    else
+      ret_str = ""
+
+      while (tag = peekNextToken)
+        if tag.kind_of?(HTMLTag) and tag.tag_name == until_tag
+          break
+        end
+
+        if ("" != tag.text)
+          ret_str << (tag.text + " ")
+        end
+        getNextToken
+      end
+
+      ret_str
+    end
+  end
+
+  # Like getText, but squeeze all whitespace, getting rid of
+  # leading and trailing whitespace, and squeezing multiple
+  # spaces into a single space.
+  def getTrimmedText(until_tag = nil)
+    getText(until_tag).strip.gsub(/\s+/m, " ")
+  end
+
+end
+
+# The parent class for all three types of HTML tokens
+class HTMLToken
+  attr_accessor :raw
+
+  # Initialize the token based on the raw text
+  def initialize(text)
+    @raw = text
+  end
+
+  # By default, return exactly the string used to create the text
+  def to_s
+    raw
+  end
+
+  # By default tokens have no text representation
+  def text
+    ""
+  end
+
+  def trimmed_text
+    text.strip.gsub(/\s+/m, " ")
+  end
+
+  # Compare to another based on the raw source
+  def ==(other)
+    raw == other.to_s
+  end
+end
+
+# Class representing text that isn't inside a tag
+class HTMLText < HTMLToken
+  def text
+    raw
+  end
+end
+
+# Class representing an HTML comment
+class HTMLComment < HTMLToken
+  attr_accessor :contents
+  def initialize(text)
+    super(text)
+    temp_arr = text.scan(/^<!--\s*(.*?)\s*-->$/m)
+    if temp_arr[0].nil?
+      raise "Text passed to HTMLComment.initialize is not a comment"
+    end
+
+    @contents = temp_arr[0][0]
+  end
+end
+
+# Class representing an HTML tag
+class HTMLTag < HTMLToken
+  attr_reader :end_tag, :tag_name
+  def initialize(text)
+    super(text)
+    if ?< != text[0] or ?> != text[-1]
+      raise "Text passed to HTMLComment.initialize is not a comment"
+    end
+
+    @attr_hash = Hash.new
+    @raw = text
+
+    tag_name = text.scan(/[\w:-]+/)[0]
+    if tag_name.nil?
+      raise "Error, tag is nil: #{tag_name}"
+    end
+
+    if ?/ == text[1]
+      # It's an end tag
+      @end_tag = true
+      @tag_name = '/' + tag_name.downcase
+    else
+      @end_tag = false
+      @tag_name = tag_name.downcase
+    end
+
+    @hashed = false
+  end
+
+  # Retrieve a hash of all the tag's attributes.
+  # Lazily done, so that if you don't look at a tag's attributes
+  # things go quicker
+  def attr_hash
+    # Lazy initialize == don't build the hash until it's needed
+    if !@hashed
+      if !@end_tag
+        # Get the attributes
+        attr_arr = @raw.scan(/<[\w:-]+\s+(.*)>/m)[0]
+        if attr_arr.kind_of?(Array)
+          # Attributes found, parse them
+          attrs = attr_arr[0]
+          attr_arr = attrs.scan(/\s*([\w:-]+)(?:\s*=\s*("[^"]*"|'[^']*'|([^"'>][^\s>]*)))?/m)
+          # clean up the array by:
+          # * setting all nil elements to true
+          # * removing enclosing quotes
+          attr_arr.each {
+            |item|
+            val = if item[1].nil?
+                    item[0]
+                  elsif '"'[0] == item[1][0] or '\''[0] == item[1][0]
+                    item[1][1 .. -2]
+                  else
+                    item[1]
+                  end
+            @attr_hash[item[0].downcase] = val
+          }
+        end
+      end
+      @hashed = true
+    end
+
+    #p self
+
+    @attr_hash
+  end
+
+  # Get the 'alt' text for a tag, if it exists, or an empty string otherwise
+  def text
+    if !end_tag
+      case tag_name
+      when 'img'
+        if !attr_hash['alt'].nil?
+          return attr_hash['alt']
+        end
+      when 'applet'
+        if !attr_hash['alt'].nil?
+          return attr_hash['alt']
+        end
+      end
+    end
+    return ''
+  end
+end
+
+if $0 == __FILE__
+  require 'test/unit'
+
+  class TC_TestHTMLTokenizer < Test::Unit::TestCase
+    def test_bad_link
+      toke = HTMLTokenizer.new("<p><a href=http://bad.com/link>foo</a></p>")
+      assert("http://bad.com/link" == toke.getTag("a").attr_hash['href'])
+    end
+
+    def test_namespace
+      toke = HTMLTokenizer.new("<f:table xmlns:f=\"http://www.com/foo\">")
+      assert("http://www.com/foo" == toke.getTag("f:table").attr_hash['xmlns:f'])
+    end
+
+    def test_comment
+      toke = HTMLTokenizer.new("<!-- comment on me -->")
+      t = toke.getNextToken
+      assert(HTMLComment == t.class)
+      assert("comment on me" == t.contents)
+    end
+
+
+    def test_full
+      page = "<HTML>
+<HEAD>
+<TITLE>This is the title</TITLE>
+</HEAD>
+<!-- Here comes the <a href=\"missing.link\">blah</a>
+comment body
+ -->
+<BODY>
+  <H1>This is the header</H1>
+  <P>
+    This is the paragraph, it contains
+    <a href=\"link.html\">links</a>, 
+    <img src=\"blah.gif\" optional alt='images
+are
+really cool'>.  Ok, here is some more text and
+    <A href=\"http://another.link.com/\" target=\"_blank\">another link</A>.
+  </P>
+</body>
+</HTML>
+"
+      toke = HTMLTokenizer.new(page)
+
+      assert("<h1>" == toke.getTag("h1", "h2", "h3").to_s.downcase)
+      assert(HTMLTag.new("<a href=\"link.html\">") == toke.getTag("IMG", "A"))
+      assert("links" == toke.getTrimmedText)
+      assert(toke.getTag("IMG", "A").attr_hash['optional'])
+      assert("_blank" == toke.getTag("IMG", "A").attr_hash['target'])
+    end
+  end
+end

Added: incubator/heraldry/libraries/ruby/openid/trunk/lib/openid/parse.rb
URL: http://svn.apache.org/viewvc/incubator/heraldry/libraries/ruby/openid/trunk/lib/openid/parse.rb?view=auto&rev=462998
==============================================================================
--- incubator/heraldry/libraries/ruby/openid/trunk/lib/openid/parse.rb (added)
+++ incubator/heraldry/libraries/ruby/openid/trunk/lib/openid/parse.rb Wed Oct 11 15:13:01 2006
@@ -0,0 +1,23 @@
+require "openid/htmltokenizer"
+
+def parse_link_attrs(data)
+  parser = HTMLTokenizer.new(data)
+  in_head = false
+  begin
+    while el = parser.getTag("head", "link", "body")
+      if el.tag_name == "head"
+        in_head = true
+      elsif el.tag_name == "link"
+        continue unless in_head
+        yield el.attr_hash
+      elsif el.tag_name == "body"
+        return
+      end
+    end
+  rescue
+    return
+  end  
+end
+
+            
+            



Mime
View raw message