]> source.dussan.org Git - redmine.git/commitdiff
Unpacked OpenID gem. #699
authorEric Davis <edavis@littlestreamsoftware.com>
Wed, 11 Feb 2009 19:06:37 +0000 (19:06 +0000)
committerEric Davis <edavis@littlestreamsoftware.com>
Wed, 11 Feb 2009 19:06:37 +0000 (19:06 +0000)
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2437 e93f8b46-1217-0410-a6f0-8f06a7374b81

191 files changed:
config/environment.rb
vendor/gems/ruby-openid-2.1.4/.specification [new file with mode: 0644]
vendor/gems/ruby-openid-2.1.4/CHANGELOG [new file with mode: 0644]
vendor/gems/ruby-openid-2.1.4/INSTALL [new file with mode: 0644]
vendor/gems/ruby-openid-2.1.4/LICENSE [new file with mode: 0644]
vendor/gems/ruby-openid-2.1.4/NOTICE [new file with mode: 0644]
vendor/gems/ruby-openid-2.1.4/README [new file with mode: 0644]
vendor/gems/ruby-openid-2.1.4/UPGRADE [new file with mode: 0644]
vendor/gems/ruby-openid-2.1.4/admin/runtests.rb [new file with mode: 0644]
vendor/gems/ruby-openid-2.1.4/examples/README [new file with mode: 0644]
vendor/gems/ruby-openid-2.1.4/examples/active_record_openid_store/README [new file with mode: 0644]
vendor/gems/ruby-openid-2.1.4/examples/active_record_openid_store/XXX_add_open_id_store_to_db.rb [new file with mode: 0644]
vendor/gems/ruby-openid-2.1.4/examples/active_record_openid_store/XXX_upgrade_open_id_store.rb [new file with mode: 0644]
vendor/gems/ruby-openid-2.1.4/examples/active_record_openid_store/init.rb [new file with mode: 0644]
vendor/gems/ruby-openid-2.1.4/examples/active_record_openid_store/lib/association.rb [new file with mode: 0644]
vendor/gems/ruby-openid-2.1.4/examples/active_record_openid_store/lib/nonce.rb [new file with mode: 0644]
vendor/gems/ruby-openid-2.1.4/examples/active_record_openid_store/lib/open_id_setting.rb [new file with mode: 0644]
vendor/gems/ruby-openid-2.1.4/examples/active_record_openid_store/lib/openid_ar_store.rb [new file with mode: 0644]
vendor/gems/ruby-openid-2.1.4/examples/active_record_openid_store/test/store_test.rb [new file with mode: 0644]
vendor/gems/ruby-openid-2.1.4/examples/discover [new file with mode: 0644]
vendor/gems/ruby-openid-2.1.4/examples/rails_openid/README [new file with mode: 0644]
vendor/gems/ruby-openid-2.1.4/examples/rails_openid/Rakefile [new file with mode: 0644]
vendor/gems/ruby-openid-2.1.4/examples/rails_openid/app/controllers/application.rb [new file with mode: 0644]
vendor/gems/ruby-openid-2.1.4/examples/rails_openid/app/controllers/consumer_controller.rb [new file with mode: 0644]
vendor/gems/ruby-openid-2.1.4/examples/rails_openid/app/controllers/login_controller.rb [new file with mode: 0644]
vendor/gems/ruby-openid-2.1.4/examples/rails_openid/app/controllers/server_controller.rb [new file with mode: 0644]
vendor/gems/ruby-openid-2.1.4/examples/rails_openid/app/helpers/application_helper.rb [new file with mode: 0644]
vendor/gems/ruby-openid-2.1.4/examples/rails_openid/app/helpers/login_helper.rb [new file with mode: 0644]
vendor/gems/ruby-openid-2.1.4/examples/rails_openid/app/helpers/server_helper.rb [new file with mode: 0644]
vendor/gems/ruby-openid-2.1.4/examples/rails_openid/app/views/consumer/index.rhtml [new file with mode: 0644]
vendor/gems/ruby-openid-2.1.4/examples/rails_openid/app/views/layouts/server.rhtml [new file with mode: 0644]
vendor/gems/ruby-openid-2.1.4/examples/rails_openid/app/views/login/index.rhtml [new file with mode: 0644]
vendor/gems/ruby-openid-2.1.4/examples/rails_openid/app/views/server/decide.rhtml [new file with mode: 0644]
vendor/gems/ruby-openid-2.1.4/examples/rails_openid/config/boot.rb [new file with mode: 0644]
vendor/gems/ruby-openid-2.1.4/examples/rails_openid/config/environment.rb [new file with mode: 0644]
vendor/gems/ruby-openid-2.1.4/examples/rails_openid/config/environments/development.rb [new file with mode: 0644]
vendor/gems/ruby-openid-2.1.4/examples/rails_openid/config/environments/production.rb [new file with mode: 0644]
vendor/gems/ruby-openid-2.1.4/examples/rails_openid/config/environments/test.rb [new file with mode: 0644]
vendor/gems/ruby-openid-2.1.4/examples/rails_openid/config/routes.rb [new file with mode: 0644]
vendor/gems/ruby-openid-2.1.4/examples/rails_openid/doc/README_FOR_APP [new file with mode: 0644]
vendor/gems/ruby-openid-2.1.4/examples/rails_openid/public/404.html [new file with mode: 0644]
vendor/gems/ruby-openid-2.1.4/examples/rails_openid/public/500.html [new file with mode: 0644]
vendor/gems/ruby-openid-2.1.4/examples/rails_openid/public/dispatch.cgi [new file with mode: 0644]
vendor/gems/ruby-openid-2.1.4/examples/rails_openid/public/dispatch.fcgi [new file with mode: 0644]
vendor/gems/ruby-openid-2.1.4/examples/rails_openid/public/dispatch.rb [new file with mode: 0644]
vendor/gems/ruby-openid-2.1.4/examples/rails_openid/public/favicon.ico [new file with mode: 0644]
vendor/gems/ruby-openid-2.1.4/examples/rails_openid/public/images/openid_login_bg.gif [new file with mode: 0644]
vendor/gems/ruby-openid-2.1.4/examples/rails_openid/public/javascripts/controls.js [new file with mode: 0644]
vendor/gems/ruby-openid-2.1.4/examples/rails_openid/public/javascripts/dragdrop.js [new file with mode: 0644]
vendor/gems/ruby-openid-2.1.4/examples/rails_openid/public/javascripts/effects.js [new file with mode: 0644]
vendor/gems/ruby-openid-2.1.4/examples/rails_openid/public/javascripts/prototype.js [new file with mode: 0644]
vendor/gems/ruby-openid-2.1.4/examples/rails_openid/public/robots.txt [new file with mode: 0644]
vendor/gems/ruby-openid-2.1.4/examples/rails_openid/script/about [new file with mode: 0644]
vendor/gems/ruby-openid-2.1.4/examples/rails_openid/script/breakpointer [new file with mode: 0644]
vendor/gems/ruby-openid-2.1.4/examples/rails_openid/script/console [new file with mode: 0644]
vendor/gems/ruby-openid-2.1.4/examples/rails_openid/script/destroy [new file with mode: 0644]
vendor/gems/ruby-openid-2.1.4/examples/rails_openid/script/generate [new file with mode: 0644]
vendor/gems/ruby-openid-2.1.4/examples/rails_openid/script/performance/benchmarker [new file with mode: 0644]
vendor/gems/ruby-openid-2.1.4/examples/rails_openid/script/performance/profiler [new file with mode: 0644]
vendor/gems/ruby-openid-2.1.4/examples/rails_openid/script/plugin [new file with mode: 0644]
vendor/gems/ruby-openid-2.1.4/examples/rails_openid/script/process/reaper [new file with mode: 0644]
vendor/gems/ruby-openid-2.1.4/examples/rails_openid/script/process/spawner [new file with mode: 0644]
vendor/gems/ruby-openid-2.1.4/examples/rails_openid/script/process/spinner [new file with mode: 0644]
vendor/gems/ruby-openid-2.1.4/examples/rails_openid/script/runner [new file with mode: 0644]
vendor/gems/ruby-openid-2.1.4/examples/rails_openid/script/server [new file with mode: 0644]
vendor/gems/ruby-openid-2.1.4/examples/rails_openid/test/functional/login_controller_test.rb [new file with mode: 0644]
vendor/gems/ruby-openid-2.1.4/examples/rails_openid/test/functional/server_controller_test.rb [new file with mode: 0644]
vendor/gems/ruby-openid-2.1.4/examples/rails_openid/test/test_helper.rb [new file with mode: 0644]
vendor/gems/ruby-openid-2.1.4/lib/hmac/hmac.rb [new file with mode: 0644]
vendor/gems/ruby-openid-2.1.4/lib/hmac/sha1.rb [new file with mode: 0644]
vendor/gems/ruby-openid-2.1.4/lib/hmac/sha2.rb [new file with mode: 0644]
vendor/gems/ruby-openid-2.1.4/lib/openid.rb [new file with mode: 0644]
vendor/gems/ruby-openid-2.1.4/lib/openid/association.rb [new file with mode: 0644]
vendor/gems/ruby-openid-2.1.4/lib/openid/consumer.rb [new file with mode: 0644]
vendor/gems/ruby-openid-2.1.4/lib/openid/consumer/associationmanager.rb [new file with mode: 0644]
vendor/gems/ruby-openid-2.1.4/lib/openid/consumer/checkid_request.rb [new file with mode: 0644]
vendor/gems/ruby-openid-2.1.4/lib/openid/consumer/discovery.rb [new file with mode: 0644]
vendor/gems/ruby-openid-2.1.4/lib/openid/consumer/discovery_manager.rb [new file with mode: 0644]
vendor/gems/ruby-openid-2.1.4/lib/openid/consumer/html_parse.rb [new file with mode: 0644]
vendor/gems/ruby-openid-2.1.4/lib/openid/consumer/idres.rb [new file with mode: 0644]
vendor/gems/ruby-openid-2.1.4/lib/openid/consumer/responses.rb [new file with mode: 0644]
vendor/gems/ruby-openid-2.1.4/lib/openid/cryptutil.rb [new file with mode: 0644]
vendor/gems/ruby-openid-2.1.4/lib/openid/dh.rb [new file with mode: 0644]
vendor/gems/ruby-openid-2.1.4/lib/openid/extension.rb [new file with mode: 0644]
vendor/gems/ruby-openid-2.1.4/lib/openid/extensions/ax.rb [new file with mode: 0644]
vendor/gems/ruby-openid-2.1.4/lib/openid/extensions/pape.rb [new file with mode: 0644]
vendor/gems/ruby-openid-2.1.4/lib/openid/extensions/sreg.rb [new file with mode: 0644]
vendor/gems/ruby-openid-2.1.4/lib/openid/extras.rb [new file with mode: 0644]
vendor/gems/ruby-openid-2.1.4/lib/openid/fetchers.rb [new file with mode: 0644]
vendor/gems/ruby-openid-2.1.4/lib/openid/kvform.rb [new file with mode: 0644]
vendor/gems/ruby-openid-2.1.4/lib/openid/kvpost.rb [new file with mode: 0644]
vendor/gems/ruby-openid-2.1.4/lib/openid/message.rb [new file with mode: 0644]
vendor/gems/ruby-openid-2.1.4/lib/openid/protocolerror.rb [new file with mode: 0644]
vendor/gems/ruby-openid-2.1.4/lib/openid/server.rb [new file with mode: 0644]
vendor/gems/ruby-openid-2.1.4/lib/openid/store/filesystem.rb [new file with mode: 0644]
vendor/gems/ruby-openid-2.1.4/lib/openid/store/interface.rb [new file with mode: 0644]
vendor/gems/ruby-openid-2.1.4/lib/openid/store/memory.rb [new file with mode: 0644]
vendor/gems/ruby-openid-2.1.4/lib/openid/store/nonce.rb [new file with mode: 0644]
vendor/gems/ruby-openid-2.1.4/lib/openid/trustroot.rb [new file with mode: 0644]
vendor/gems/ruby-openid-2.1.4/lib/openid/urinorm.rb [new file with mode: 0644]
vendor/gems/ruby-openid-2.1.4/lib/openid/util.rb [new file with mode: 0644]
vendor/gems/ruby-openid-2.1.4/lib/openid/yadis/accept.rb [new file with mode: 0644]
vendor/gems/ruby-openid-2.1.4/lib/openid/yadis/constants.rb [new file with mode: 0644]
vendor/gems/ruby-openid-2.1.4/lib/openid/yadis/discovery.rb [new file with mode: 0644]
vendor/gems/ruby-openid-2.1.4/lib/openid/yadis/filters.rb [new file with mode: 0644]
vendor/gems/ruby-openid-2.1.4/lib/openid/yadis/htmltokenizer.rb [new file with mode: 0644]
vendor/gems/ruby-openid-2.1.4/lib/openid/yadis/parsehtml.rb [new file with mode: 0644]
vendor/gems/ruby-openid-2.1.4/lib/openid/yadis/services.rb [new file with mode: 0644]
vendor/gems/ruby-openid-2.1.4/lib/openid/yadis/xrds.rb [new file with mode: 0644]
vendor/gems/ruby-openid-2.1.4/lib/openid/yadis/xri.rb [new file with mode: 0644]
vendor/gems/ruby-openid-2.1.4/lib/openid/yadis/xrires.rb [new file with mode: 0644]
vendor/gems/ruby-openid-2.1.4/test/data/accept.txt [new file with mode: 0644]
vendor/gems/ruby-openid-2.1.4/test/data/dh.txt [new file with mode: 0644]
vendor/gems/ruby-openid-2.1.4/test/data/example-xrds.xml [new file with mode: 0644]
vendor/gems/ruby-openid-2.1.4/test/data/linkparse.txt [new file with mode: 0644]
vendor/gems/ruby-openid-2.1.4/test/data/n2b64 [new file with mode: 0644]
vendor/gems/ruby-openid-2.1.4/test/data/test1-discover.txt [new file with mode: 0644]
vendor/gems/ruby-openid-2.1.4/test/data/test1-parsehtml.txt [new file with mode: 0644]
vendor/gems/ruby-openid-2.1.4/test/data/test_discover/malformed_meta_tag.html [new file with mode: 0644]
vendor/gems/ruby-openid-2.1.4/test/data/test_discover/openid.html [new file with mode: 0644]
vendor/gems/ruby-openid-2.1.4/test/data/test_discover/openid2.html [new file with mode: 0644]
vendor/gems/ruby-openid-2.1.4/test/data/test_discover/openid2_xrds.xml [new file with mode: 0644]
vendor/gems/ruby-openid-2.1.4/test/data/test_discover/openid2_xrds_no_local_id.xml [new file with mode: 0644]
vendor/gems/ruby-openid-2.1.4/test/data/test_discover/openid_1_and_2.html [new file with mode: 0644]
vendor/gems/ruby-openid-2.1.4/test/data/test_discover/openid_1_and_2_xrds.xml [new file with mode: 0644]
vendor/gems/ruby-openid-2.1.4/test/data/test_discover/openid_1_and_2_xrds_bad_delegate.xml [new file with mode: 0644]
vendor/gems/ruby-openid-2.1.4/test/data/test_discover/openid_and_yadis.html [new file with mode: 0644]
vendor/gems/ruby-openid-2.1.4/test/data/test_discover/openid_no_delegate.html [new file with mode: 0644]
vendor/gems/ruby-openid-2.1.4/test/data/test_discover/yadis_0entries.xml [new file with mode: 0644]
vendor/gems/ruby-openid-2.1.4/test/data/test_discover/yadis_2_bad_local_id.xml [new file with mode: 0644]
vendor/gems/ruby-openid-2.1.4/test/data/test_discover/yadis_2entries_delegate.xml [new file with mode: 0644]
vendor/gems/ruby-openid-2.1.4/test/data/test_discover/yadis_2entries_idp.xml [new file with mode: 0644]
vendor/gems/ruby-openid-2.1.4/test/data/test_discover/yadis_another_delegate.xml [new file with mode: 0644]
vendor/gems/ruby-openid-2.1.4/test/data/test_discover/yadis_idp.xml [new file with mode: 0644]
vendor/gems/ruby-openid-2.1.4/test/data/test_discover/yadis_idp_delegate.xml [new file with mode: 0644]
vendor/gems/ruby-openid-2.1.4/test/data/test_discover/yadis_no_delegate.xml [new file with mode: 0644]
vendor/gems/ruby-openid-2.1.4/test/data/test_xrds/=j3h.2007.11.14.xrds [new file with mode: 0644]
vendor/gems/ruby-openid-2.1.4/test/data/test_xrds/README [new file with mode: 0644]
vendor/gems/ruby-openid-2.1.4/test/data/test_xrds/delegated-20060809-r1.xrds [new file with mode: 0644]
vendor/gems/ruby-openid-2.1.4/test/data/test_xrds/delegated-20060809-r2.xrds [new file with mode: 0644]
vendor/gems/ruby-openid-2.1.4/test/data/test_xrds/delegated-20060809.xrds [new file with mode: 0644]
vendor/gems/ruby-openid-2.1.4/test/data/test_xrds/no-xrd.xml [new file with mode: 0644]
vendor/gems/ruby-openid-2.1.4/test/data/test_xrds/not-xrds.xml [new file with mode: 0644]
vendor/gems/ruby-openid-2.1.4/test/data/test_xrds/prefixsometimes.xrds [new file with mode: 0644]
vendor/gems/ruby-openid-2.1.4/test/data/test_xrds/ref.xrds [new file with mode: 0644]
vendor/gems/ruby-openid-2.1.4/test/data/test_xrds/sometimesprefix.xrds [new file with mode: 0644]
vendor/gems/ruby-openid-2.1.4/test/data/test_xrds/spoof1.xrds [new file with mode: 0644]
vendor/gems/ruby-openid-2.1.4/test/data/test_xrds/spoof2.xrds [new file with mode: 0644]
vendor/gems/ruby-openid-2.1.4/test/data/test_xrds/spoof3.xrds [new file with mode: 0644]
vendor/gems/ruby-openid-2.1.4/test/data/test_xrds/status222.xrds [new file with mode: 0644]
vendor/gems/ruby-openid-2.1.4/test/data/test_xrds/subsegments.xrds [new file with mode: 0644]
vendor/gems/ruby-openid-2.1.4/test/data/test_xrds/valid-populated-xrds.xml [new file with mode: 0644]
vendor/gems/ruby-openid-2.1.4/test/data/trustroot.txt [new file with mode: 0644]
vendor/gems/ruby-openid-2.1.4/test/data/urinorm.txt [new file with mode: 0644]
vendor/gems/ruby-openid-2.1.4/test/discoverdata.rb [new file with mode: 0644]
vendor/gems/ruby-openid-2.1.4/test/test_accept.rb [new file with mode: 0644]
vendor/gems/ruby-openid-2.1.4/test/test_association.rb [new file with mode: 0644]
vendor/gems/ruby-openid-2.1.4/test/test_associationmanager.rb [new file with mode: 0644]
vendor/gems/ruby-openid-2.1.4/test/test_ax.rb [new file with mode: 0644]
vendor/gems/ruby-openid-2.1.4/test/test_checkid_request.rb [new file with mode: 0644]
vendor/gems/ruby-openid-2.1.4/test/test_consumer.rb [new file with mode: 0644]
vendor/gems/ruby-openid-2.1.4/test/test_cryptutil.rb [new file with mode: 0644]
vendor/gems/ruby-openid-2.1.4/test/test_dh.rb [new file with mode: 0644]
vendor/gems/ruby-openid-2.1.4/test/test_discover.rb [new file with mode: 0644]
vendor/gems/ruby-openid-2.1.4/test/test_discovery_manager.rb [new file with mode: 0644]
vendor/gems/ruby-openid-2.1.4/test/test_extension.rb [new file with mode: 0644]
vendor/gems/ruby-openid-2.1.4/test/test_extras.rb [new file with mode: 0644]
vendor/gems/ruby-openid-2.1.4/test/test_fetchers.rb [new file with mode: 0644]
vendor/gems/ruby-openid-2.1.4/test/test_filters.rb [new file with mode: 0644]
vendor/gems/ruby-openid-2.1.4/test/test_idres.rb [new file with mode: 0644]
vendor/gems/ruby-openid-2.1.4/test/test_kvform.rb [new file with mode: 0644]
vendor/gems/ruby-openid-2.1.4/test/test_kvpost.rb [new file with mode: 0644]
vendor/gems/ruby-openid-2.1.4/test/test_linkparse.rb [new file with mode: 0644]
vendor/gems/ruby-openid-2.1.4/test/test_message.rb [new file with mode: 0644]
vendor/gems/ruby-openid-2.1.4/test/test_nonce.rb [new file with mode: 0644]
vendor/gems/ruby-openid-2.1.4/test/test_openid_yadis.rb [new file with mode: 0644]
vendor/gems/ruby-openid-2.1.4/test/test_pape.rb [new file with mode: 0644]
vendor/gems/ruby-openid-2.1.4/test/test_parsehtml.rb [new file with mode: 0644]
vendor/gems/ruby-openid-2.1.4/test/test_responses.rb [new file with mode: 0644]
vendor/gems/ruby-openid-2.1.4/test/test_server.rb [new file with mode: 0644]
vendor/gems/ruby-openid-2.1.4/test/test_sreg.rb [new file with mode: 0644]
vendor/gems/ruby-openid-2.1.4/test/test_stores.rb [new file with mode: 0644]
vendor/gems/ruby-openid-2.1.4/test/test_trustroot.rb [new file with mode: 0644]
vendor/gems/ruby-openid-2.1.4/test/test_urinorm.rb [new file with mode: 0644]
vendor/gems/ruby-openid-2.1.4/test/test_util.rb [new file with mode: 0644]
vendor/gems/ruby-openid-2.1.4/test/test_xrds.rb [new file with mode: 0644]
vendor/gems/ruby-openid-2.1.4/test/test_xri.rb [new file with mode: 0644]
vendor/gems/ruby-openid-2.1.4/test/test_xrires.rb [new file with mode: 0644]
vendor/gems/ruby-openid-2.1.4/test/test_yadis_discovery.rb [new file with mode: 0644]
vendor/gems/ruby-openid-2.1.4/test/testutil.rb [new file with mode: 0644]
vendor/gems/ruby-openid-2.1.4/test/util.rb [new file with mode: 0644]

index fdac6b4f037b8f3e5bdacbe40f79f3485ae30ab2..70d0c55aab0b7e53a806f1a8ceba181fc6277b0b 100644 (file)
@@ -54,4 +54,6 @@ Rails::Initializer.run do |config|
   # Define your email configuration in email.yml instead.
   # It will automatically turn deliveries on
   config.action_mailer.perform_deliveries = false
+  
+  config.gem 'ruby-openid', :lib => 'openid'
 end
diff --git a/vendor/gems/ruby-openid-2.1.4/.specification b/vendor/gems/ruby-openid-2.1.4/.specification
new file mode 100644 (file)
index 0000000..489cda0
--- /dev/null
@@ -0,0 +1,290 @@
+--- !ruby/object:Gem::Specification 
+name: ruby-openid
+version: !ruby/object:Gem::Version 
+  version: 2.1.4
+platform: ruby
+authors: 
+- JanRain, Inc
+autorequire: openid
+bindir: bin
+cert_chain: 
+date: 2008-12-19 00:00:00 -08:00
+default_executable: 
+dependencies: []
+
+description: 
+email: openid@janrain.com
+executables: []
+
+extensions: []
+
+extra_rdoc_files: 
+- README
+- INSTALL
+- LICENSE
+- UPGRADE
+files: 
+- examples/README
+- examples/active_record_openid_store
+- examples/rails_openid
+- examples/discover
+- examples/active_record_openid_store/lib
+- examples/active_record_openid_store/test
+- examples/active_record_openid_store/init.rb
+- examples/active_record_openid_store/README
+- examples/active_record_openid_store/XXX_add_open_id_store_to_db.rb
+- examples/active_record_openid_store/XXX_upgrade_open_id_store.rb
+- examples/active_record_openid_store/lib/association.rb
+- examples/active_record_openid_store/lib/nonce.rb
+- examples/active_record_openid_store/lib/open_id_setting.rb
+- examples/active_record_openid_store/lib/openid_ar_store.rb
+- examples/active_record_openid_store/test/store_test.rb
+- examples/rails_openid/app
+- examples/rails_openid/components
+- examples/rails_openid/config
+- examples/rails_openid/db
+- examples/rails_openid/doc
+- examples/rails_openid/lib
+- examples/rails_openid/log
+- examples/rails_openid/public
+- examples/rails_openid/script
+- examples/rails_openid/test
+- examples/rails_openid/vendor
+- examples/rails_openid/Rakefile
+- examples/rails_openid/README
+- examples/rails_openid/app/controllers
+- examples/rails_openid/app/helpers
+- examples/rails_openid/app/models
+- examples/rails_openid/app/views
+- examples/rails_openid/app/controllers/application.rb
+- examples/rails_openid/app/controllers/login_controller.rb
+- examples/rails_openid/app/controllers/server_controller.rb
+- examples/rails_openid/app/controllers/consumer_controller.rb
+- examples/rails_openid/app/helpers/application_helper.rb
+- examples/rails_openid/app/helpers/login_helper.rb
+- examples/rails_openid/app/helpers/server_helper.rb
+- examples/rails_openid/app/views/layouts
+- examples/rails_openid/app/views/login
+- examples/rails_openid/app/views/server
+- examples/rails_openid/app/views/consumer
+- examples/rails_openid/app/views/layouts/server.rhtml
+- examples/rails_openid/app/views/login/index.rhtml
+- examples/rails_openid/app/views/server/decide.rhtml
+- examples/rails_openid/app/views/consumer/index.rhtml
+- examples/rails_openid/config/environments
+- examples/rails_openid/config/database.yml
+- examples/rails_openid/config/boot.rb
+- examples/rails_openid/config/environment.rb
+- examples/rails_openid/config/routes.rb
+- examples/rails_openid/config/environments/development.rb
+- examples/rails_openid/config/environments/production.rb
+- examples/rails_openid/config/environments/test.rb
+- examples/rails_openid/doc/README_FOR_APP
+- examples/rails_openid/lib/tasks
+- examples/rails_openid/public/images
+- examples/rails_openid/public/javascripts
+- examples/rails_openid/public/stylesheets
+- examples/rails_openid/public/dispatch.cgi
+- examples/rails_openid/public/404.html
+- examples/rails_openid/public/500.html
+- examples/rails_openid/public/dispatch.fcgi
+- examples/rails_openid/public/dispatch.rb
+- examples/rails_openid/public/favicon.ico
+- examples/rails_openid/public/robots.txt
+- examples/rails_openid/public/images/openid_login_bg.gif
+- examples/rails_openid/public/javascripts/controls.js
+- examples/rails_openid/public/javascripts/dragdrop.js
+- examples/rails_openid/public/javascripts/effects.js
+- examples/rails_openid/public/javascripts/prototype.js
+- examples/rails_openid/script/performance
+- examples/rails_openid/script/process
+- examples/rails_openid/script/console
+- examples/rails_openid/script/about
+- examples/rails_openid/script/breakpointer
+- examples/rails_openid/script/destroy
+- examples/rails_openid/script/generate
+- examples/rails_openid/script/plugin
+- examples/rails_openid/script/runner
+- examples/rails_openid/script/server
+- examples/rails_openid/script/performance/benchmarker
+- examples/rails_openid/script/performance/profiler
+- examples/rails_openid/script/process/spawner
+- examples/rails_openid/script/process/reaper
+- examples/rails_openid/script/process/spinner
+- examples/rails_openid/test/fixtures
+- examples/rails_openid/test/functional
+- examples/rails_openid/test/mocks
+- examples/rails_openid/test/unit
+- examples/rails_openid/test/test_helper.rb
+- examples/rails_openid/test/functional/login_controller_test.rb
+- examples/rails_openid/test/functional/server_controller_test.rb
+- examples/rails_openid/test/mocks/development
+- examples/rails_openid/test/mocks/test
+- lib/openid
+- lib/hmac
+- lib/openid.rb
+- lib/openid/cryptutil.rb
+- lib/openid/extras.rb
+- lib/openid/urinorm.rb
+- lib/openid/util.rb
+- lib/openid/trustroot.rb
+- lib/openid/message.rb
+- lib/openid/yadis
+- lib/openid/consumer
+- lib/openid/fetchers.rb
+- lib/openid/dh.rb
+- lib/openid/kvform.rb
+- lib/openid/association.rb
+- lib/openid/store
+- lib/openid/kvpost.rb
+- lib/openid/extensions
+- lib/openid/protocolerror.rb
+- lib/openid/server.rb
+- lib/openid/extension.rb
+- lib/openid/consumer.rb
+- lib/openid/yadis/htmltokenizer.rb
+- lib/openid/yadis/parsehtml.rb
+- lib/openid/yadis/filters.rb
+- lib/openid/yadis/xrds.rb
+- lib/openid/yadis/accept.rb
+- lib/openid/yadis/constants.rb
+- lib/openid/yadis/discovery.rb
+- lib/openid/yadis/xri.rb
+- lib/openid/yadis/xrires.rb
+- lib/openid/yadis/services.rb
+- lib/openid/consumer/html_parse.rb
+- lib/openid/consumer/idres.rb
+- lib/openid/consumer/associationmanager.rb
+- lib/openid/consumer/discovery.rb
+- lib/openid/consumer/discovery_manager.rb
+- lib/openid/consumer/checkid_request.rb
+- lib/openid/consumer/responses.rb
+- lib/openid/store/filesystem.rb
+- lib/openid/store/interface.rb
+- lib/openid/store/nonce.rb
+- lib/openid/store/memory.rb
+- lib/openid/extensions/sreg.rb
+- lib/openid/extensions/ax.rb
+- lib/openid/extensions/pape.rb
+- lib/hmac/hmac.rb
+- lib/hmac/sha1.rb
+- lib/hmac/sha2.rb
+- test/data
+- test/test_association.rb
+- test/test_urinorm.rb
+- test/testutil.rb
+- test/test_util.rb
+- test/test_message.rb
+- test/test_cryptutil.rb
+- test/test_extras.rb
+- test/util.rb
+- test/test_trustroot.rb
+- test/test_parsehtml.rb
+- test/test_fetchers.rb
+- test/test_dh.rb
+- test/test_kvform.rb
+- test/test_openid_yadis.rb
+- test/test_linkparse.rb
+- test/test_stores.rb
+- test/test_filters.rb
+- test/test_xrds.rb
+- test/test_nonce.rb
+- test/test_accept.rb
+- test/test_kvpost.rb
+- test/test_associationmanager.rb
+- test/discoverdata.rb
+- test/test_server.rb
+- test/test_yadis_discovery.rb
+- test/test_sreg.rb
+- test/test_idres.rb
+- test/test_ax.rb
+- test/test_xri.rb
+- test/test_xrires.rb
+- test/test_discover.rb
+- test/test_consumer.rb
+- test/test_pape.rb
+- test/test_checkid_request.rb
+- test/test_discovery_manager.rb
+- test/test_responses.rb
+- test/test_extension.rb
+- test/data/test_xrds
+- test/data/urinorm.txt
+- test/data/n2b64
+- test/data/trustroot.txt
+- test/data/dh.txt
+- test/data/test1-parsehtml.txt
+- test/data/linkparse.txt
+- test/data/accept.txt
+- test/data/test_discover
+- test/data/example-xrds.xml
+- test/data/test1-discover.txt
+- test/data/test_xrds/ref.xrds
+- test/data/test_xrds/README
+- test/data/test_xrds/delegated-20060809-r1.xrds
+- test/data/test_xrds/delegated-20060809-r2.xrds
+- test/data/test_xrds/delegated-20060809.xrds
+- test/data/test_xrds/no-xrd.xml
+- test/data/test_xrds/not-xrds.xml
+- test/data/test_xrds/prefixsometimes.xrds
+- test/data/test_xrds/sometimesprefix.xrds
+- test/data/test_xrds/spoof1.xrds
+- test/data/test_xrds/spoof2.xrds
+- test/data/test_xrds/spoof3.xrds
+- test/data/test_xrds/status222.xrds
+- test/data/test_xrds/valid-populated-xrds.xml
+- test/data/test_xrds/=j3h.2007.11.14.xrds
+- test/data/test_xrds/subsegments.xrds
+- test/data/test_discover/openid2_xrds.xml
+- test/data/test_discover/openid.html
+- test/data/test_discover/openid2.html
+- test/data/test_discover/openid2_xrds_no_local_id.xml
+- test/data/test_discover/openid_1_and_2.html
+- test/data/test_discover/openid_1_and_2_xrds.xml
+- test/data/test_discover/openid_and_yadis.html
+- test/data/test_discover/openid_1_and_2_xrds_bad_delegate.xml
+- test/data/test_discover/openid_no_delegate.html
+- test/data/test_discover/yadis_0entries.xml
+- test/data/test_discover/yadis_2_bad_local_id.xml
+- test/data/test_discover/yadis_2entries_delegate.xml
+- test/data/test_discover/yadis_2entries_idp.xml
+- test/data/test_discover/yadis_another_delegate.xml
+- test/data/test_discover/yadis_idp.xml
+- test/data/test_discover/yadis_idp_delegate.xml
+- test/data/test_discover/yadis_no_delegate.xml
+- test/data/test_discover/malformed_meta_tag.html
+- NOTICE
+- CHANGELOG
+- README
+- INSTALL
+- LICENSE
+- UPGRADE
+- admin/runtests.rb
+has_rdoc: true
+homepage: http://openidenabled.com/ruby-openid/
+post_install_message: 
+rdoc_options: 
+- --main
+- README
+require_paths: 
+- lib
+required_ruby_version: !ruby/object:Gem::Requirement 
+  requirements: 
+  - - ">"
+    - !ruby/object:Gem::Version 
+      version: 0.0.0
+  version: 
+required_rubygems_version: !ruby/object:Gem::Requirement 
+  requirements: 
+  - - ">="
+    - !ruby/object:Gem::Version 
+      version: "0"
+  version: 
+requirements: []
+
+rubyforge_project: 
+rubygems_version: 1.3.1
+signing_key: 
+specification_version: 1
+summary: A library for consuming and serving OpenID identities.
+test_files: 
+- admin/runtests.rb
diff --git a/vendor/gems/ruby-openid-2.1.4/CHANGELOG b/vendor/gems/ruby-openid-2.1.4/CHANGELOG
new file mode 100644 (file)
index 0000000..123db58
--- /dev/null
@@ -0,0 +1,11 @@
+Fri Dec 19 11:50:10 PST 2008  cygnus@janrain.com
+  tagged 2.1.4
+
+Fri Dec 19 11:48:25 PST 2008  cygnus@janrain.com
+  * Version: 2.1.4
+
+Fri Dec 19 11:42:47 PST 2008  cygnus@janrain.com
+  * Normalize XRIs when doing discovery in accordance with the OpenID 2 spec
+
+Tue Dec 16 13:14:07 PST 2008  cygnus@janrain.com
+  tagged 2.1.3
diff --git a/vendor/gems/ruby-openid-2.1.4/INSTALL b/vendor/gems/ruby-openid-2.1.4/INSTALL
new file mode 100644 (file)
index 0000000..89f1b9b
--- /dev/null
@@ -0,0 +1,47 @@
+= Ruby OpenID Library Installation
+
+== Rubygems Installation
+
+Rubygems is a tool for installing ruby libraries and their
+dependancies.  If you have rubygems installed, simply:
+
+  gem install ruby-openid
+
+== Manual Installation
+
+Unpack the archive and run setup.rb to install:
+
+ ruby setup.rb
+
+setup.rb installs the library into your system ruby.  If don't want to
+add openid to you system ruby, you may instead add the *lib* directory of
+the extracted tarball to your RUBYLIB environment variable:
+
+  $ export RUBYLIB=${RUBYLIB}:/path/to/ruby-openid/lib
+
+
+== Testing the Installation
+
+Make sure everything installed ok:
+ $> irb
+ irb$> require "openid"
+ => true
+
+Or, if you installed via rubygems:
+
+ $> irb
+ irb$> require "rubygems"
+ => true
+ irb$> require_gem "ruby-openid" 
+ => true
+
+== Run the test suite
+
+Go into the test directory and execute the *runtests.rb* script.
+
+== Next steps
+
+* Run consumer.rb in the examples directory. 
+* Get started writing your own consumer using OpenID::Consumer
+* Write your own server with OpenID::Server
+* Use the OpenIDLoginGenerator!  Read example/README for more info.
diff --git a/vendor/gems/ruby-openid-2.1.4/LICENSE b/vendor/gems/ruby-openid-2.1.4/LICENSE
new file mode 100644 (file)
index 0000000..c1c5773
--- /dev/null
@@ -0,0 +1,210 @@
+The code in lib/hmac/ is Copyright 2001 by Daiki Ueno, and distributed under
+the terms of the Ruby license.  See http://www.ruby-lang.org/en/LICENSE.txt
+
+lib/openid/yadis/htmltokenizer.rb is Copyright 2004 by Ben Giddings and
+distributed under the terms of the Ruby license.
+
+The remainder of this package is Copyright 2006-2008 by JanRain, Inc. and
+distributed under the terms of license below:
+
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "[]"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright [yyyy] [name of copyright owner]
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
diff --git a/vendor/gems/ruby-openid-2.1.4/NOTICE b/vendor/gems/ruby-openid-2.1.4/NOTICE
new file mode 100644 (file)
index 0000000..62df93b
--- /dev/null
@@ -0,0 +1,2 @@
+This product includes software developed by JanRain,
+available from http://openidenabled.com/
diff --git a/vendor/gems/ruby-openid-2.1.4/README b/vendor/gems/ruby-openid-2.1.4/README
new file mode 100644 (file)
index 0000000..c6e833b
--- /dev/null
@@ -0,0 +1,82 @@
+=Ruby OpenID
+
+A Ruby library for verifying and serving OpenID identities.
+
+==Features
+* Easy to use API for verifying OpenID identites - OpenID::Consumer
+* Support for serving OpenID identites - OpenID::Server
+* Does not depend on underlying web framework
+* Supports multiple storage mechanisms (Filesystem, ActiveRecord, Memory)
+* Example code to help you get started, including:
+  * Ruby on Rails based consumer and server
+  * OpenIDLoginGenerator for quickly getting creating a rails app that uses
+    OpenID for authentication
+  * ActiveRecordOpenIDStore plugin
+* Comprehensive test suite
+* Supports both OpenID 1 and OpenID 2 transparently
+
+==Installing
+Before running the examples or writing your own code you'll need to install
+the library.  See the INSTALL file or use rubygems:
+
+  gem install ruby-openid
+  
+Check the installation:
+  
+  $ irb
+  irb> require 'rubygems'
+  irb> require_gem 'ruby-openid'
+  => true
+
+The library is known to work with Ruby 1.8.4 on Unix, Max OSX and
+Win32.  Examples have been tested with Rails 1.1 and 1.2, and 2.0.
+
+==Getting Started
+The best way to start is to look at the rails_openid example.
+You can run it with:
+ cd examples/rails_openid
+ script/server
+
+If you are writing an OpenID Relying Party, a good place to start is:
+examples/rails_openid/app/controllers/consumer_controller.rb
+
+And if you are writing an OpenID provider:
+examples/rails_openid/app/controllers/server_controller.rb
+
+The library code is quite well documented, so don't be squeamish, and
+look at the library itself if there's anything you don't understand in
+the examples.
+
+==Homepage
+http://openidenabled.com/ruby-openid/
+
+See also:
+http://openid.net/
+http://openidenabled.com/
+
+==Community
+Discussion regarding the Ruby OpenID library and other JanRain OpenID
+libraries takes place on the the OpenID mailing list on
+openidenabled.com.
+
+http://lists.openidenabled.com/mailman/listinfo/dev
+
+Please join this list to discuss, ask implementation questions, report
+bugs, etc.  Also check out the openid channel on the freenode IRC
+network.
+
+If you have a bugfix or feature you'd like to contribute, don't
+hesitate to send it to us.  For more detailed information on how to
+contribute, see
+
+  http://openidenabled.com/contribute/
+
+==Author
+Copyright 2006-2008, JanRain, Inc.
+
+Contact openid@janrain.com or visit the OpenID channel on pibb.com:
+
+http://pibb.com/go/openid
+
+==License
+Apache Software License.  For more information see the LICENSE file.
diff --git a/vendor/gems/ruby-openid-2.1.4/UPGRADE b/vendor/gems/ruby-openid-2.1.4/UPGRADE
new file mode 100644 (file)
index 0000000..b80e823
--- /dev/null
@@ -0,0 +1,127 @@
+= Upgrading from the OpenID 1.x series library
+
+== Consumer Upgrade
+
+The flow is largely the same, however there are a number of significant 
+changes.  The consumer example is helpful to look at:
+examples/rails_openid/app/controllers/consumer_controller.rb
+
+
+=== Stores
+
+You will need to require the file for the store that you are using.
+For the filesystem store, this is 'openid/stores/filesystem'
+They are also now in modules.  The filesystem store is 
+  OpenID::Store::Filesystem
+The format has changed, and you should remove your old store directory.
+
+The ActiveRecord store ( examples/active_record_openid_store ) still needs
+to be put in a plugin directory for your rails app.  There's a migration
+that needs to be run; examine the README in that directory.
+
+Also, note that the stores now can be garbage collected with the method
+  store.cleanup
+
+
+=== Starting the OpenID transaction
+
+The OpenIDRequest object no longer has status codes.  Instead,
+consumer.begin raises an OpenID::OpenIDError if there is a problem
+initiating the transaction, so you'll want something along the lines of:
+
+  begin
+    openid_request = consumer.begin(params[:openid_identifier])
+  rescue OpenID::OpenIDError => e
+    # display error e
+    return
+  end
+  #success case
+
+Data regarding the OpenID server once lived in
+  openid_request.service
+
+The corresponding object in the 2.0 lib can be retrieved with
+  openid_request.endpoint
+
+Getting the unverified identifier: Where you once had
+  openid_request.identity_url
+you will now want
+  openid_request.endpoint.claimed_id
+which might be different from what you get at the end of the transaction,
+since it is now possible for users to enter their server's url directly.
+
+Arguments on the return_to URL are now verified, so if you want to add
+additional arguments to the return_to url, use
+  openid_request.return_to_args['param'] = value
+
+Generating the redirect is the same as before, but add any extensions
+first.
+
+If you need to set up an SSL certificate authority list for the fetcher,
+use the 'ca_file' attr_accessor on the OpenID::StandardFetcher.  This has
+changed from 'ca_path' in the 1.x.x series library.  That is, set
+OpenID.fetcher.ca_file = '/path/to/ca.list'
+before calling consumer.begin.
+
+=== Requesting Simple Registration Data
+
+You'll need to require the code for the extension
+  require 'openid/extensions/sreg'
+
+The new code for adding an SReg request now looks like:
+
+  sreg_request = OpenID::SReg::Request.new
+  sreg_request.request_fields(['email', 'dob'], true) # required
+  sreg_request.request_fields(['nickname', 'fullname'], false) # optional
+  sreg_request.policy_url = policy_url
+  openid_request.add_extension(sreg_request)
+
+The code for adding other extensions is similar.  Code for the Attribute
+Exchange (AX) and Provider Authentication Policy Extension (PAPE) are
+included with the library, and additional extensions can be implemented
+subclassing OpenID::Extension.
+
+
+=== Completing the transaction
+
+The return_to and its arguments are verified, so you need to pass in
+the base URL and the arguments.  With Rails, the params method mashes
+together parameters from GET, POST, and the path, so you'll need to pull
+off the path "parameters" with something like
+
+  return_to = url_for(:only_path => false, 
+                      :controller => 'openid',
+                      :action => 'complete')
+  parameters = params.reject{|k,v| request.path_parameters[k] }
+  openid_response = consumer.complete(parameters, return_to)
+
+The response still uses the status codes, but they are now namespaced
+slightly differently, for example OpenID::Consumer::SUCCESS
+
+In the case of failure, the error message is now found in
+  openid_response.message
+
+The identifier to display to the user can be found in
+  openid_response.endpoint.display_identifier
+
+The Simple Registration response can be read from the OpenID response
+with
+  sreg_response = OpenID::SReg::Response.from_success_response(openid_response)
+  nickname = sreg_response['nickname']
+  # etc.
+
+
+== Server Upgrade
+
+The server code is mostly the same as before, with the exception of
+extensions.  Also, you must pass in the endpoint URL to the server 
+constructor:
+  @server = OpenID::Server.new(store, server_url)
+
+I recommend looking at 
+examples/rails_openid/app/controllers/server_controller.rb
+for an example of the new way of doing extensions.
+
+--
+Dag Arneson, JanRain Inc.
+Please direct questions to openid@janrain.com
diff --git a/vendor/gems/ruby-openid-2.1.4/admin/runtests.rb b/vendor/gems/ruby-openid-2.1.4/admin/runtests.rb
new file mode 100644 (file)
index 0000000..50abe04
--- /dev/null
@@ -0,0 +1,36 @@
+#!/usr/bin/ruby
+
+require "logger"
+require "stringio"
+require "pathname"
+
+require 'test/unit/collector/dir'
+require 'test/unit/ui/console/testrunner'
+
+def main
+  old_verbose = $VERBOSE
+  $VERBOSE = true
+
+  tests_dir = Pathname.new(__FILE__).dirname.dirname.join('test')
+
+  # Collect tests from everything named test_*.rb.
+  c = Test::Unit::Collector::Dir.new
+
+  if c.respond_to?(:base=)
+    # In order to supress warnings from ruby 1.8.6 about accessing
+    # undefined member
+    c.base = tests_dir
+    suite = c.collect
+  else
+    # Because base is not defined in ruby < 1.8.6
+    suite = c.collect(tests_dir)
+  end
+
+
+  result = Test::Unit::UI::Console::TestRunner.run(suite)
+  result.passed?
+ensure
+  $VERBOSE = old_verbose
+end
+
+exit(main)
diff --git a/vendor/gems/ruby-openid-2.1.4/examples/README b/vendor/gems/ruby-openid-2.1.4/examples/README
new file mode 100644 (file)
index 0000000..71aa30d
--- /dev/null
@@ -0,0 +1,32 @@
+This directory contains several examples that demonstrate use of the
+OpenID library.  Make sure you have properly installed the library
+before running the examples.  These examples are a great place to
+start in integrating OpenID into your application.
+
+==Rails example
+
+The rails_openid contains a fully functional OpenID server and relying
+party, and acts as a starting point for implementing your own
+production rails server.  You'll need the latest version of Ruby on
+Rails installed, and then:
+
+ cd rails_openid
+ ./script/server
+
+Open a web browser to http://localhost:3000/ and follow the instructions.
+
+The relevant code to work from when writing your Rails OpenID Relying
+Party is: 
+  rails_openid/app/controllers/consumer_controller.rb
+If you are working on an OpenID provider, check out
+  rails_openid/app/controllers/server_controller.rb
+
+Since the library and examples are Apache-licensed, don't be shy about 
+copy-and-paste.
+
+==Rails ActiveRecord OpenIDStore plugin
+
+For various reasons you may want or need to deploy your ruby openid
+consumer/server using an SQL based store.  The active_record_openid_store 
+is a plugin that makes using an SQL based store simple.  Follow the
+README inside the plugin's dir for usage.
diff --git a/vendor/gems/ruby-openid-2.1.4/examples/active_record_openid_store/README b/vendor/gems/ruby-openid-2.1.4/examples/active_record_openid_store/README
new file mode 100644 (file)
index 0000000..1178729
--- /dev/null
@@ -0,0 +1,58 @@
+=Active Record OpenID Store Plugin
+
+A store is required by an OpenID server and optionally by the consumer
+to store associations, nonces, and auth key information across
+requests and processes.  If rails is distributed across several
+machines, they must must all have access to the same OpenID store
+data, so the FilesystemStore won't do.
+
+This directory contains a plugin for connecting your
+OpenID enabled rails app to an ActiveRecord based OpenID store.
+
+==Install
+
+1) Copy this directory and all it's contents into your
+RAILS_ROOT/vendor/plugins directory.  You structure should look like
+this:
+
+  RAILS_ROOT/vendor/plugins/active_record_openid_store/
+
+2) Copy the migration, XXX_add_open_id_store_to_db.rb to your
+   RAILS_ROOT/db/migrate directory.  Rename the XXX portion of the
+   file to next sequential migration number.
+
+3) Run the migration:
+
+  rake migrate
+
+4) Change your app to use the ActiveRecordOpenIDStore:
+
+  store = ActiveRecordOpenIDStore.new
+  consumer = OpenID::Consumer.new(session, store)
+
+5) That's it! All your OpenID state will now be stored in the database.
+
+==Upgrade
+
+If you are upgrading from the 1.x ActiveRecord store, replace your old
+RAILS_ROOT/vendor/plugins/active_record_openid_store/ directory with
+the new one and run the migration XXX_upgrade_open_id_store.rb.
+
+==What about garbage collection? 
+
+You may garbage collect unused nonces and expired associations using
+the gc instance method of ActiveRecordOpenIDStore.  Hook it up to a
+task in your app's Rakefile like so:
+
+  desc 'GC OpenID store'
+  task :gc_openid_store => :environment do
+    ActiveRecordOpenIDStore.new.cleanup
+  end
+
+Run it by typing:
+
+  rake gc_openid_store
+
+
+==Questions?
+Contact Dag Arneson: dag at janrain dot com
diff --git a/vendor/gems/ruby-openid-2.1.4/examples/active_record_openid_store/XXX_add_open_id_store_to_db.rb b/vendor/gems/ruby-openid-2.1.4/examples/active_record_openid_store/XXX_add_open_id_store_to_db.rb
new file mode 100644 (file)
index 0000000..9962545
--- /dev/null
@@ -0,0 +1,24 @@
+# Use this migration to create the tables for the ActiveRecord store
+class AddOpenIdStoreToDb < ActiveRecord::Migration
+  def self.up
+    create_table "open_id_associations", :force => true do |t|
+      t.column "server_url", :binary, :null => false
+      t.column "handle", :string, :null => false
+      t.column "secret", :binary, :null => false
+      t.column "issued", :integer, :null => false
+      t.column "lifetime", :integer, :null => false
+      t.column "assoc_type", :string, :null => false
+    end
+
+    create_table "open_id_nonces", :force => true do |t|
+      t.column :server_url, :string, :null => false
+      t.column :timestamp, :integer, :null => false
+      t.column :salt, :string, :null => false
+    end
+  end
+
+  def self.down
+    drop_table "open_id_associations"
+    drop_table "open_id_nonces"
+  end
+end
diff --git a/vendor/gems/ruby-openid-2.1.4/examples/active_record_openid_store/XXX_upgrade_open_id_store.rb b/vendor/gems/ruby-openid-2.1.4/examples/active_record_openid_store/XXX_upgrade_open_id_store.rb
new file mode 100644 (file)
index 0000000..273d285
--- /dev/null
@@ -0,0 +1,26 @@
+# Use this migration to upgrade the old 1.1 ActiveRecord store schema
+# to the new 2.0 schema.
+class UpgradeOpenIdStore < ActiveRecord::Migration
+  def self.up
+    drop_table "open_id_settings"
+    drop_table "open_id_nonces"
+    create_table "open_id_nonces", :force => true do |t|
+      t.column :server_url, :string, :null => false
+      t.column :timestamp, :integer, :null => false
+      t.column :salt, :string, :null => false
+    end
+  end
+
+  def self.down
+    drop_table "open_id_nonces"
+    create_table "open_id_nonces", :force => true do |t|
+      t.column "nonce", :string
+      t.column "created", :integer
+    end
+
+    create_table "open_id_settings", :force => true do |t|
+      t.column "setting", :string
+      t.column "value", :binary
+    end
+  end
+end
diff --git a/vendor/gems/ruby-openid-2.1.4/examples/active_record_openid_store/init.rb b/vendor/gems/ruby-openid-2.1.4/examples/active_record_openid_store/init.rb
new file mode 100644 (file)
index 0000000..b625179
--- /dev/null
@@ -0,0 +1,8 @@
+# might using the ruby-openid gem
+begin
+  require 'rubygems'
+rescue LoadError
+  nil
+end
+require 'openid'
+require 'openid_ar_store'
diff --git a/vendor/gems/ruby-openid-2.1.4/examples/active_record_openid_store/lib/association.rb b/vendor/gems/ruby-openid-2.1.4/examples/active_record_openid_store/lib/association.rb
new file mode 100644 (file)
index 0000000..09eda8b
--- /dev/null
@@ -0,0 +1,10 @@
+require 'openid/association'
+require 'time'
+
+class Association < ActiveRecord::Base
+  set_table_name 'open_id_associations'
+  def from_record
+    OpenID::Association.new(handle, secret, Time.at(issued), lifetime, assoc_type)
+  end
+end
+
diff --git a/vendor/gems/ruby-openid-2.1.4/examples/active_record_openid_store/lib/nonce.rb b/vendor/gems/ruby-openid-2.1.4/examples/active_record_openid_store/lib/nonce.rb
new file mode 100644 (file)
index 0000000..fcf5153
--- /dev/null
@@ -0,0 +1,3 @@
+class Nonce < ActiveRecord::Base
+  set_table_name 'open_id_nonces'
+end
diff --git a/vendor/gems/ruby-openid-2.1.4/examples/active_record_openid_store/lib/open_id_setting.rb b/vendor/gems/ruby-openid-2.1.4/examples/active_record_openid_store/lib/open_id_setting.rb
new file mode 100644 (file)
index 0000000..030e4c2
--- /dev/null
@@ -0,0 +1,4 @@
+class OpenIdSetting < ActiveRecord::Base
+  
+  validates_uniqueness_of :setting
+end
diff --git a/vendor/gems/ruby-openid-2.1.4/examples/active_record_openid_store/lib/openid_ar_store.rb b/vendor/gems/ruby-openid-2.1.4/examples/active_record_openid_store/lib/openid_ar_store.rb
new file mode 100644 (file)
index 0000000..276569c
--- /dev/null
@@ -0,0 +1,57 @@
+require 'association'
+require 'nonce'
+require 'openid/store/interface'
+
+# not in OpenID module to avoid namespace conflict
+class ActiveRecordStore < OpenID::Store::Interface
+  def store_association(server_url, assoc)
+    remove_association(server_url, assoc.handle)    
+    Association.create!(:server_url => server_url,
+                       :handle     => assoc.handle,
+                       :secret     => assoc.secret,
+                       :issued     => assoc.issued.to_i,
+                       :lifetime   => assoc.lifetime,
+                       :assoc_type => assoc.assoc_type)
+  end
+
+  def get_association(server_url, handle=nil)
+    assocs = if handle.blank?
+        Association.find_all_by_server_url(server_url)
+      else
+        Association.find_all_by_server_url_and_handle(server_url, handle)
+      end
+
+    assocs.reverse.each do |assoc|
+      a = assoc.from_record    
+      if a.expires_in == 0
+        assoc.destroy
+      else
+        return a
+      end
+    end if assocs.any?
+    
+    return nil
+  end
+  
+  def remove_association(server_url, handle)
+    Association.delete_all(['server_url = ? AND handle = ?', server_url, handle]) > 0
+  end
+  
+  def use_nonce(server_url, timestamp, salt)
+    return false if Nonce.find_by_server_url_and_timestamp_and_salt(server_url, timestamp, salt)
+    return false if (timestamp - Time.now.to_i).abs > OpenID::Nonce.skew
+    Nonce.create!(:server_url => server_url, :timestamp => timestamp, :salt => salt)
+    return true
+  end
+  
+  def cleanup_nonces
+    now = Time.now.to_i
+    Nonce.delete_all(["timestamp > ? OR timestamp < ?", now + OpenID::Nonce.skew, now - OpenID::Nonce.skew])
+  end
+
+  def cleanup_associations
+    now = Time.now.to_i
+    Association.delete_all(['issued + lifetime > ?',now])
+  end
+
+end
diff --git a/vendor/gems/ruby-openid-2.1.4/examples/active_record_openid_store/test/store_test.rb b/vendor/gems/ruby-openid-2.1.4/examples/active_record_openid_store/test/store_test.rb
new file mode 100644 (file)
index 0000000..8e1986c
--- /dev/null
@@ -0,0 +1,212 @@
+$:.unshift(File.dirname(__FILE__) + '/../lib')
+require 'test/unit'
+RAILS_ENV = "test"
+require File.expand_path(File.join(File.dirname(__FILE__), '../../../../config/environment.rb'))
+
+module StoreTestCase
+  @@allowed_handle = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~'
+  @@allowed_nonce = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
+  
+  def _gen_nonce
+    OpenID::CryptUtil.random_string(8, @@allowed_nonce)
+  end
+
+  def _gen_handle(n)
+    OpenID::CryptUtil.random_string(n, @@allowed_handle)
+  end
+
+  def _gen_secret(n, chars=nil)
+    OpenID::CryptUtil.random_string(n, chars)
+  end
+
+  def _gen_assoc(issued, lifetime=600)
+    secret = _gen_secret(20)
+    handle = _gen_handle(128)
+    OpenID::Association.new(handle, secret, Time.now + issued, lifetime,
+                            'HMAC-SHA1') 
+  end
+  
+  def _check_retrieve(url, handle=nil, expected=nil)
+    ret_assoc = @store.get_association(url, handle)
+
+    if expected.nil?
+      assert_nil(ret_assoc)
+    else
+      assert_equal(expected, ret_assoc)
+      assert_equal(expected.handle, ret_assoc.handle)
+      assert_equal(expected.secret, ret_assoc.secret)
+    end
+  end
+
+  def _check_remove(url, handle, expected)
+    present = @store.remove_association(url, handle)
+    assert_equal(expected, present)
+  end
+
+  def test_store
+    server_url = "http://www.myopenid.com/openid"
+    assoc = _gen_assoc(issued=0)
+
+    # Make sure that a missing association returns no result
+    _check_retrieve(server_url)
+
+    # Check that after storage, getting returns the same result
+    @store.store_association(server_url, assoc)
+    _check_retrieve(server_url, nil, assoc)
+
+    # more than once
+    _check_retrieve(server_url, nil, assoc)
+
+    # Storing more than once has no ill effect
+    @store.store_association(server_url, assoc)
+    _check_retrieve(server_url, nil, assoc)
+
+    # Removing an association that does not exist returns not present
+    _check_remove(server_url, assoc.handle + 'x', false)
+
+    # Removing an association that does not exist returns not present
+    _check_remove(server_url + 'x', assoc.handle, false)
+
+    # Removing an association that is present returns present
+    _check_remove(server_url, assoc.handle, true)
+
+    # but not present on subsequent calls
+    _check_remove(server_url, assoc.handle, false)
+
+    # Put assoc back in the store
+    @store.store_association(server_url, assoc)
+
+    # More recent and expires after assoc
+    assoc2 = _gen_assoc(issued=1)
+    @store.store_association(server_url, assoc2)
+
+    # After storing an association with a different handle, but the
+    # same server_url, the handle with the later expiration is returned.
+    _check_retrieve(server_url, nil, assoc2)
+
+    # We can still retrieve the older association
+    _check_retrieve(server_url, assoc.handle, assoc)
+
+    # Plus we can retrieve the association with the later expiration
+    # explicitly
+    _check_retrieve(server_url, assoc2.handle, assoc2)
+
+    # More recent, and expires earlier than assoc2 or assoc. Make sure
+    # that we're picking the one with the latest issued date and not
+    # taking into account the expiration.
+    assoc3 = _gen_assoc(issued=2, lifetime=100)
+    @store.store_association(server_url, assoc3)
+
+    _check_retrieve(server_url, nil, assoc3)
+    _check_retrieve(server_url, assoc.handle, assoc)
+    _check_retrieve(server_url, assoc2.handle, assoc2)
+    _check_retrieve(server_url, assoc3.handle, assoc3)
+
+    _check_remove(server_url, assoc2.handle, true)
+
+    _check_retrieve(server_url, nil, assoc3)
+    _check_retrieve(server_url, assoc.handle, assoc)
+    _check_retrieve(server_url, assoc2.handle, nil)
+    _check_retrieve(server_url, assoc3.handle, assoc3)
+
+    _check_remove(server_url, assoc2.handle, false)
+    _check_remove(server_url, assoc3.handle, true)
+
+    _check_retrieve(server_url, nil, assoc)
+    _check_retrieve(server_url, assoc.handle, assoc)
+    _check_retrieve(server_url, assoc2.handle, nil)
+    _check_retrieve(server_url, assoc3.handle, nil)
+
+    _check_remove(server_url, assoc2.handle, false)
+    _check_remove(server_url, assoc.handle, true)
+    _check_remove(server_url, assoc3.handle, false)
+
+    _check_retrieve(server_url, nil, nil)
+    _check_retrieve(server_url, assoc.handle, nil)
+    _check_retrieve(server_url, assoc2.handle, nil)
+    _check_retrieve(server_url, assoc3.handle, nil)
+
+    _check_remove(server_url, assoc2.handle, false)
+    _check_remove(server_url, assoc.handle, false)
+    _check_remove(server_url, assoc3.handle, false)
+
+    assocValid1 = _gen_assoc(-3600, 7200)
+    assocValid2 = _gen_assoc(-5)
+    assocExpired1 = _gen_assoc(-7200, 3600)
+    assocExpired2 = _gen_assoc(-7200, 3600)
+
+    @store.cleanup_associations
+    @store.store_association(server_url + '1', assocValid1)
+    @store.store_association(server_url + '1', assocExpired1)
+    @store.store_association(server_url + '2', assocExpired2)
+    @store.store_association(server_url + '3', assocValid2)
+
+    cleaned = @store.cleanup_associations()
+    assert_equal(2, cleaned, "cleaned up associations")
+  end
+
+  def _check_use_nonce(nonce, expected, server_url, msg='')
+    stamp, salt = OpenID::Nonce::split_nonce(nonce)
+    actual = @store.use_nonce(server_url, stamp, salt)
+    assert_equal(expected, actual, msg)
+  end
+
+  def test_nonce
+    server_url = "http://www.myopenid.com/openid"
+    [server_url, ''].each{|url|
+      nonce1 = OpenID::Nonce::mk_nonce
+
+      _check_use_nonce(nonce1, true, url, "#{url}: nonce allowed by default") 
+      _check_use_nonce(nonce1, false, url, "#{url}: nonce not allowed twice") 
+      _check_use_nonce(nonce1, false, url, "#{url}: nonce not allowed third time")
+      
+      # old nonces shouldn't pass
+      old_nonce = OpenID::Nonce::mk_nonce(3600)
+      _check_use_nonce(old_nonce, false, url, "Old nonce #{old_nonce.inspect} passed")
+
+    }
+
+    now = Time.now.to_i
+    old_nonce1 = OpenID::Nonce::mk_nonce(now - 20000)
+    old_nonce2 = OpenID::Nonce::mk_nonce(now - 10000)
+    recent_nonce = OpenID::Nonce::mk_nonce(now - 600)
+
+    orig_skew = OpenID::Nonce.skew
+    OpenID::Nonce.skew = 0
+    count = @store.cleanup_nonces
+    OpenID::Nonce.skew = 1000000
+    ts, salt = OpenID::Nonce::split_nonce(old_nonce1)
+    assert(@store.use_nonce(server_url, ts, salt), "oldnonce1")
+    ts, salt = OpenID::Nonce::split_nonce(old_nonce2)
+    assert(@store.use_nonce(server_url, ts, salt), "oldnonce2")
+    ts, salt = OpenID::Nonce::split_nonce(recent_nonce)
+    assert(@store.use_nonce(server_url, ts, salt), "recent_nonce")
+
+    
+    OpenID::Nonce.skew = 1000
+    cleaned = @store.cleanup_nonces
+    assert_equal(2, cleaned, "Cleaned #{cleaned} nonces")
+
+    OpenID::Nonce.skew = 100000
+    ts, salt = OpenID::Nonce::split_nonce(old_nonce1)
+    assert(@store.use_nonce(server_url, ts, salt), "oldnonce1 after cleanup")
+    ts, salt = OpenID::Nonce::split_nonce(old_nonce2)
+    assert(@store.use_nonce(server_url, ts, salt), "oldnonce2 after cleanup")
+    ts, salt = OpenID::Nonce::split_nonce(recent_nonce)
+    assert(!@store.use_nonce(server_url, ts, salt), "recent_nonce after cleanup")
+
+    OpenID::Nonce.skew = orig_skew
+
+  end
+end
+
+
+class TestARStore < Test::Unit::TestCase
+  include StoreTestCase
+  
+  def setup
+    @store = ActiveRecordStore.new
+  end
+
+end
+
diff --git a/vendor/gems/ruby-openid-2.1.4/examples/discover b/vendor/gems/ruby-openid-2.1.4/examples/discover
new file mode 100644 (file)
index 0000000..ab985a4
--- /dev/null
@@ -0,0 +1,49 @@
+#!/usr/bin/env ruby
+require "openid/consumer/discovery"
+require 'openid/fetchers'
+
+OpenID::fetcher_use_env_http_proxy
+
+$names = [[:server_url,   "Server URL  "],
+          [:local_id,     "Local ID    "],
+          [:canonical_id, "Canonical ID"],
+         ]
+
+def show_services(user_input, normalized, services)
+  puts " Claimed identifier: #{normalized}"
+  if services.empty?
+    puts " No OpenID services found"
+    puts
+  else
+    puts " Discovered services:"
+    n = 0
+    services.each do |service|
+      n += 1
+      puts "  #{n}."
+      $names.each do |meth, name|
+        val = service.send(meth)
+        if val
+          printf("     %s: %s\n", name, val)
+        end
+      end
+      puts "     Type URIs:"
+      for type_uri in service.type_uris
+        puts "       * #{type_uri}"
+      end
+      puts
+    end
+  end
+end
+
+ARGV.each do |openid_identifier|
+  puts "=" * 50
+  puts "Running discovery on #{openid_identifier}"
+  begin
+    normalized_identifier, services = OpenID.discover(openid_identifier)
+  rescue OpenID::DiscoveryFailure => why
+    puts "Discovery failed: #{why.message}"
+    puts
+  else
+    show_services(openid_identifier, normalized_identifier, services)
+  end
+end
diff --git a/vendor/gems/ruby-openid-2.1.4/examples/rails_openid/README b/vendor/gems/ruby-openid-2.1.4/examples/rails_openid/README
new file mode 100644 (file)
index 0000000..cd9d0ff
--- /dev/null
@@ -0,0 +1,153 @@
+== Welcome to Rails
+
+Rails is a web-application and persistence framework that includes everything
+needed to create database-backed web-applications according to the
+Model-View-Control pattern of separation. This pattern splits the view (also
+called the presentation) into "dumb" templates that are primarily responsible
+for inserting pre-built data in between HTML tags. The model contains the
+"smart" domain objects (such as Account, Product, Person, Post) that holds all
+the business logic and knows how to persist themselves to a database. The
+controller handles the incoming requests (such as Save New Account, Update
+Product, Show Post) by manipulating the model and directing data to the view.
+
+In Rails, the model is handled by what's called an object-relational mapping
+layer entitled Active Record. This layer allows you to present the data from
+database rows as objects and embellish these data objects with business logic
+methods. You can read more about Active Record in 
+link:files/vendor/rails/activerecord/README.html.
+
+The controller and view are handled by the Action Pack, which handles both
+layers by its two parts: Action View and Action Controller. These two layers
+are bundled in a single package due to their heavy interdependence. This is
+unlike the relationship between the Active Record and Action Pack that is much
+more separate. Each of these packages can be used independently outside of
+Rails.  You can read more about Action Pack in 
+link:files/vendor/rails/actionpack/README.html.
+
+
+== Getting started
+
+1. Run the WEBrick servlet: <tt>ruby script/server</tt> (run with --help for options)
+   ...or if you have lighttpd installed: <tt>ruby script/lighttpd</tt> (it's faster)
+2. Go to http://localhost:3000/ and get "Congratulations, you've put Ruby on Rails!"
+3. Follow the guidelines on the "Congratulations, you've put Ruby on Rails!" screen
+
+
+== Example for Apache conf
+
+  <VirtualHost *:80>
+    ServerName rails
+    DocumentRoot /path/application/public/
+    ErrorLog /path/application/log/server.log
+  
+    <Directory /path/application/public/>
+      Options ExecCGI FollowSymLinks
+      AllowOverride all
+      Allow from all
+      Order allow,deny
+    </Directory>
+  </VirtualHost>
+
+NOTE: Be sure that CGIs can be executed in that directory as well. So ExecCGI
+should be on and ".cgi" should respond. All requests from 127.0.0.1 go
+through CGI, so no Apache restart is necessary for changes. All other requests
+go through FCGI (or mod_ruby), which requires a restart to show changes.
+
+
+== Debugging Rails
+
+Have "tail -f" commands running on both the server.log, production.log, and
+test.log files. Rails will automatically display debugging and runtime
+information to these files. Debugging info will also be shown in the browser
+on requests from 127.0.0.1.
+
+
+== Breakpoints
+
+Breakpoint support is available through the script/breakpointer client. This
+means that you can break out of execution at any point in the code, investigate
+and change the model, AND then resume execution! Example:
+
+  class WeblogController < ActionController::Base
+    def index
+      @posts = Post.find_all
+      breakpoint "Breaking out from the list"
+    end
+  end
+  
+So the controller will accept the action, run the first line, then present you
+with a IRB prompt in the breakpointer window. Here you can do things like:
+
+Executing breakpoint "Breaking out from the list" at .../webrick_server.rb:16 in 'breakpoint'
+
+  >> @posts.inspect
+  => "[#<Post:0x14a6be8 @attributes={\"title\"=>nil, \"body\"=>nil, \"id\"=>\"1\"}>, 
+       #<Post:0x14a6620 @attributes={\"title\"=>\"Rails you know!\", \"body\"=>\"Only ten..\", \"id\"=>\"2\"}>]"
+  >> @posts.first.title = "hello from a breakpoint"
+  => "hello from a breakpoint"
+
+...and even better is that you can examine how your runtime objects actually work:
+
+  >> f = @posts.first 
+  => #<Post:0x13630c4 @attributes={"title"=>nil, "body"=>nil, "id"=>"1"}>
+  >> f.
+  Display all 152 possibilities? (y or n)
+
+Finally, when you're ready to resume execution, you press CTRL-D
+
+
+== Console
+
+You can interact with the domain model by starting the console through script/console. 
+Here you'll have all parts of the application configured, just like it is when the
+application is running. You can inspect domain models, change values, and save to the
+database. Starting the script without arguments will launch it in the development environment.
+Passing an argument will specify a different environment, like <tt>console production</tt>.
+
+
+== Description of contents
+
+app
+  Holds all the code that's specific to this particular application.
+
+app/controllers
+  Holds controllers that should be named like weblog_controller.rb for
+  automated URL mapping. All controllers should descend from
+  ActionController::Base.
+
+app/models
+  Holds models that should be named like post.rb.
+  Most models will descend from ActiveRecord::Base.
+  
+app/views
+  Holds the template files for the view that should be named like
+  weblog/index.rhtml for the WeblogController#index action. All views use eRuby
+  syntax. This directory can also be used to keep stylesheets, images, and so on
+  that can be symlinked to public.
+  
+app/helpers
+  Holds view helpers that should be named like weblog_helper.rb.
+
+config
+  Configuration files for the Rails environment, the routing map, the database, and other dependencies.
+
+components
+  Self-contained mini-applications that can bundle together controllers, models, and views.
+
+lib
+  Application specific libraries. Basically, any kind of custom code that doesn't
+  belong under controllers, models, or helpers. This directory is in the load path.
+    
+public
+  The directory available for the web server. Contains subdirectories for images, stylesheets,
+  and javascripts. Also contains the dispatchers and the default HTML files.
+
+script
+  Helper scripts for automation and generation.
+
+test
+  Unit and functional tests along with fixtures.
+
+vendor
+  External libraries that the application depends on. Also includes the plugins subdirectory.
+  This directory is in the load path.
diff --git a/vendor/gems/ruby-openid-2.1.4/examples/rails_openid/Rakefile b/vendor/gems/ruby-openid-2.1.4/examples/rails_openid/Rakefile
new file mode 100644 (file)
index 0000000..cffd19f
--- /dev/null
@@ -0,0 +1,10 @@
+# Add your own tasks in files placed in lib/tasks ending in .rake,
+# for example lib/tasks/switchtower.rake, and they will automatically be available to Rake.
+
+require(File.join(File.dirname(__FILE__), 'config', 'boot'))
+
+require 'rake'
+require 'rake/testtask'
+require 'rake/rdoctask'
+
+require 'tasks/rails'
\ No newline at end of file
diff --git a/vendor/gems/ruby-openid-2.1.4/examples/rails_openid/app/controllers/application.rb b/vendor/gems/ruby-openid-2.1.4/examples/rails_openid/app/controllers/application.rb
new file mode 100644 (file)
index 0000000..537de40
--- /dev/null
@@ -0,0 +1,4 @@
+# Filters added to this controller will be run for all controllers in the application.
+# Likewise, all the methods added will be available for all controllers.
+class ApplicationController < ActionController::Base
+end
\ No newline at end of file
diff --git a/vendor/gems/ruby-openid-2.1.4/examples/rails_openid/app/controllers/consumer_controller.rb b/vendor/gems/ruby-openid-2.1.4/examples/rails_openid/app/controllers/consumer_controller.rb
new file mode 100644 (file)
index 0000000..37dd3bb
--- /dev/null
@@ -0,0 +1,122 @@
+require 'pathname'
+
+require "openid"
+require 'openid/extensions/sreg'
+require 'openid/extensions/pape'
+require 'openid/store/filesystem'
+
+class ConsumerController < ApplicationController
+  layout nil
+
+  def index
+    # render an openid form
+  end
+
+  def start
+    begin
+      identifier = params[:openid_identifier]
+      if identifier.nil?
+        flash[:error] = "Enter an OpenID identifier"
+        redirect_to :action => 'index'
+        return
+      end
+      oidreq = consumer.begin(identifier)
+    rescue OpenID::OpenIDError => e
+      flash[:error] = "Discovery failed for #{identifier}: #{e}"
+      redirect_to :action => 'index'
+      return
+    end
+    if params[:use_sreg]
+      sregreq = OpenID::SReg::Request.new
+      # required fields
+      sregreq.request_fields(['email','nickname'], true)
+      # optional fields
+      sregreq.request_fields(['dob', 'fullname'], false)
+      oidreq.add_extension(sregreq)
+      oidreq.return_to_args['did_sreg'] = 'y'
+    end
+    if params[:use_pape]
+      papereq = OpenID::PAPE::Request.new
+      papereq.add_policy_uri(OpenID::PAPE::AUTH_PHISHING_RESISTANT)
+      papereq.max_auth_age = 2*60*60
+      oidreq.add_extension(papereq)
+      oidreq.return_to_args['did_pape'] = 'y'
+    end
+    if params[:force_post]
+      oidreq.return_to_args['force_post']='x'*2048
+    end
+    return_to = url_for :action => 'complete', :only_path => false
+    realm = url_for :action => 'index', :only_path => false
+    
+    if oidreq.send_redirect?(realm, return_to, params[:immediate])
+      redirect_to oidreq.redirect_url(realm, return_to, params[:immediate])
+    else
+      render :text => oidreq.html_markup(realm, return_to, params[:immediate], {'id' => 'openid_form'})
+    end
+  end
+
+  def complete
+    # FIXME - url_for some action is not necessarily the current URL.
+    current_url = url_for(:action => 'complete', :only_path => false)
+    parameters = params.reject{|k,v|request.path_parameters[k]}
+    oidresp = consumer.complete(parameters, current_url)
+    case oidresp.status
+    when OpenID::Consumer::FAILURE
+      if oidresp.display_identifier
+        flash[:error] = ("Verification of #{oidresp.display_identifier}"\
+                         " failed: #{oidresp.message}")
+      else
+        flash[:error] = "Verification failed: #{oidresp.message}"
+      end
+    when OpenID::Consumer::SUCCESS
+      flash[:success] = ("Verification of #{oidresp.display_identifier}"\
+                         " succeeded.")
+      if params[:did_sreg]
+        sreg_resp = OpenID::SReg::Response.from_success_response(oidresp)
+        sreg_message = "Simple Registration data was requested"
+        if sreg_resp.empty?
+          sreg_message << ", but none was returned."
+        else
+          sreg_message << ". The following data were sent:"
+          sreg_resp.data.each {|k,v|
+            sreg_message << "<br/><b>#{k}</b>: #{v}"
+          }
+        end
+        flash[:sreg_results] = sreg_message
+      end
+      if params[:did_pape]
+        pape_resp = OpenID::PAPE::Response.from_success_response(oidresp)
+        pape_message = "A phishing resistant authentication method was requested"
+        if pape_resp.auth_policies.member? OpenID::PAPE::AUTH_PHISHING_RESISTANT
+          pape_message << ", and the server reported one."
+        else
+          pape_message << ", but the server did not report one."
+        end
+        if pape_resp.auth_time
+          pape_message << "<br><b>Authentication time:</b> #{pape_resp.auth_time} seconds"
+        end
+        if pape_resp.nist_auth_level
+          pape_message << "<br><b>NIST Auth Level:</b> #{pape_resp.nist_auth_level}"
+        end
+        flash[:pape_results] = pape_message
+      end
+    when OpenID::Consumer::SETUP_NEEDED
+      flash[:alert] = "Immediate request failed - Setup Needed"
+    when OpenID::Consumer::CANCEL
+      flash[:alert] = "OpenID transaction cancelled."
+    else
+    end
+    redirect_to :action => 'index'
+  end
+
+  private
+
+  def consumer
+    if @consumer.nil?
+      dir = Pathname.new(RAILS_ROOT).join('db').join('cstore')
+      store = OpenID::Store::Filesystem.new(dir)
+      @consumer = OpenID::Consumer.new(session, store)
+    end
+    return @consumer
+  end
+end
diff --git a/vendor/gems/ruby-openid-2.1.4/examples/rails_openid/app/controllers/login_controller.rb b/vendor/gems/ruby-openid-2.1.4/examples/rails_openid/app/controllers/login_controller.rb
new file mode 100644 (file)
index 0000000..ff7c257
--- /dev/null
@@ -0,0 +1,45 @@
+# Controller for handling the login, logout process for "users" of our
+# little server.  Users have no password.  This is just an example.
+
+require 'openid'
+
+class LoginController < ApplicationController
+
+  layout 'server'
+
+  def base_url
+    url_for(:controller => 'login', :action => nil, :only_path => false)
+  end
+
+  def index
+    response.headers['X-XRDS-Location'] = url_for(:controller => "server",
+                                                  :action => "idp_xrds",
+                                                  :only_path => false)
+    @base_url = base_url
+    # just show the login page
+  end
+
+  def submit
+    user = params[:username]
+
+    # if we get a user, log them in by putting their username in
+    # the session hash.
+    unless user.nil?
+      session[:username] = user unless user.nil?
+      session[:approvals] = []
+      flash[:notice] = "Your OpenID URL is <b>#{base_url}user/#{user}</b><br/><br/>Proceed to step 2 below."
+    else
+      flash[:error] = "Sorry, couldn't log you in. Try again."
+    end
+    
+    redirect_to :action => 'index'
+  end
+
+  def logout
+    # delete the username from the session hash
+    session[:username] = nil
+    session[:approvals] = nil
+    redirect_to :action => 'index'
+  end
+
+end
diff --git a/vendor/gems/ruby-openid-2.1.4/examples/rails_openid/app/controllers/server_controller.rb b/vendor/gems/ruby-openid-2.1.4/examples/rails_openid/app/controllers/server_controller.rb
new file mode 100644 (file)
index 0000000..af0b1a7
--- /dev/null
@@ -0,0 +1,265 @@
+require 'pathname'
+
+# load the openid library, first trying rubygems
+#begin
+#  require "rubygems"
+#  require_gem "ruby-openid", ">= 1.0"
+#rescue LoadError
+require "openid"
+require "openid/consumer/discovery"
+require 'openid/extensions/sreg'
+require 'openid/extensions/pape'
+require 'openid/store/filesystem'
+#end
+
+class ServerController < ApplicationController
+
+  include ServerHelper
+  include OpenID::Server
+  layout nil
+
+  def index
+    begin
+      oidreq = server.decode_request(params)
+    rescue ProtocolError => e
+      # invalid openid request, so just display a page with an error message
+      render :text => e.to_s, :status => 500
+      return
+    end
+
+    # no openid.mode was given
+    unless oidreq
+      render :text => "This is an OpenID server endpoint."
+      return
+    end
+
+    oidresp = nil
+
+    if oidreq.kind_of?(CheckIDRequest)
+
+      identity = oidreq.identity
+
+      if oidreq.id_select
+        if oidreq.immediate
+          oidresp = oidreq.answer(false)
+        elsif session[:username].nil?
+          # The user hasn't logged in.
+          show_decision_page(oidreq)
+          return
+        else
+          # Else, set the identity to the one the user is using.
+          identity = url_for_user
+        end
+      end
+
+      if oidresp
+        nil
+      elsif self.is_authorized(identity, oidreq.trust_root)
+        oidresp = oidreq.answer(true, nil, identity)
+
+        # add the sreg response if requested
+        add_sreg(oidreq, oidresp)
+        # ditto pape
+        add_pape(oidreq, oidresp)
+
+      elsif oidreq.immediate
+        server_url = url_for :action => 'index'
+        oidresp = oidreq.answer(false, server_url)
+
+      else
+        show_decision_page(oidreq)
+        return
+      end
+
+    else
+      oidresp = server.handle_request(oidreq)
+    end
+
+    self.render_response(oidresp)
+  end
+
+  def show_decision_page(oidreq, message="Do you trust this site with your identity?")
+    session[:last_oidreq] = oidreq
+    @oidreq = oidreq
+
+    if message
+      flash[:notice] = message
+    end
+
+    render :template => 'server/decide', :layout => 'server'
+  end
+
+  def user_page
+    # Yadis content-negotiation: we want to return the xrds if asked for.
+    accept = request.env['HTTP_ACCEPT']
+
+    # This is not technically correct, and should eventually be updated
+    # to do real Accept header parsing and logic.  Though I expect it will work
+    # 99% of the time.
+    if accept and accept.include?('application/xrds+xml')
+      user_xrds
+      return
+    end
+
+    # content negotiation failed, so just render the user page
+    xrds_url = url_for(:controller=>'user',:action=>params[:username])+'/xrds'
+    identity_page = <<EOS
+<html><head>
+<meta http-equiv="X-XRDS-Location" content="#{xrds_url}" />
+<link rel="openid.server" href="#{url_for :action => 'index'}" />
+</head><body><p>OpenID identity page for #{params[:username]}</p>
+</body></html>
+EOS
+
+    # Also add the Yadis location header, so that they don't have
+    # to parse the html unless absolutely necessary.
+    response.headers['X-XRDS-Location'] = xrds_url
+    render :text => identity_page
+  end
+
+  def user_xrds
+    types = [
+             OpenID::OPENID_2_0_TYPE,
+             OpenID::OPENID_1_0_TYPE,
+             OpenID::SREG_URI,
+            ]
+
+    render_xrds(types)
+  end
+
+  def idp_xrds
+    types = [
+             OpenID::OPENID_IDP_2_0_TYPE,
+            ]
+
+    render_xrds(types)
+  end
+
+  def decision
+    oidreq = session[:last_oidreq]
+    session[:last_oidreq] = nil
+
+    if params[:yes].nil?
+      redirect_to oidreq.cancel_url
+      return
+    else
+      id_to_send = params[:id_to_send]
+
+      identity = oidreq.identity
+      if oidreq.id_select
+        if id_to_send and id_to_send != ""
+          session[:username] = id_to_send
+          session[:approvals] = []
+          identity = url_for_user
+        else
+          msg = "You must enter a username to in order to send " +
+            "an identifier to the Relying Party."
+          show_decision_page(oidreq, msg)
+          return
+        end
+      end
+
+      if session[:approvals]
+        session[:approvals] << oidreq.trust_root
+      else
+        session[:approvals] = [oidreq.trust_root]
+      end
+      oidresp = oidreq.answer(true, nil, identity)
+      add_sreg(oidreq, oidresp)
+      add_pape(oidreq, oidresp)
+      return self.render_response(oidresp)
+    end
+  end
+
+  protected
+
+  def server
+    if @server.nil?
+      server_url = url_for :action => 'index', :only_path => false
+      dir = Pathname.new(RAILS_ROOT).join('db').join('openid-store')
+      store = OpenID::Store::Filesystem.new(dir)
+      @server = Server.new(store, server_url)
+    end
+    return @server
+  end
+
+  def approved(trust_root)
+    return false if session[:approvals].nil?
+    return session[:approvals].member?(trust_root)
+  end
+
+  def is_authorized(identity_url, trust_root)
+    return (session[:username] and (identity_url == url_for_user) and self.approved(trust_root))
+  end
+
+  def render_xrds(types)
+    type_str = ""
+
+    types.each { |uri|
+      type_str += "<Type>#{uri}</Type>\n      "
+    }
+
+    yadis = <<EOS
+<?xml version="1.0" encoding="UTF-8"?>
+<xrds:XRDS
+    xmlns:xrds="xri://$xrds"
+    xmlns="xri://$xrd*($v*2.0)">
+  <XRD>
+    <Service priority="0">
+      #{type_str}
+      <URI>#{url_for(:controller => 'server', :only_path => false)}</URI>
+    </Service>
+  </XRD>
+</xrds:XRDS>
+EOS
+
+    response.headers['content-type'] = 'application/xrds+xml'
+    render :text => yadis
+  end
+
+  def add_sreg(oidreq, oidresp)
+    # check for Simple Registration arguments and respond
+    sregreq = OpenID::SReg::Request.from_openid_request(oidreq)
+
+    return if sregreq.nil?
+    # In a real application, this data would be user-specific,
+    # and the user should be asked for permission to release
+    # it.
+    sreg_data = {
+      'nickname' => session[:username],
+      'fullname' => 'Mayor McCheese',
+      'email' => 'mayor@example.com'
+    }
+
+    sregresp = OpenID::SReg::Response.extract_response(sregreq, sreg_data)
+    oidresp.add_extension(sregresp)
+  end
+
+  def add_pape(oidreq, oidresp)
+    papereq = OpenID::PAPE::Request.from_openid_request(oidreq)
+    return if papereq.nil?
+    paperesp = OpenID::PAPE::Response.new
+    paperesp.nist_auth_level = 0 # we don't even do auth at all!
+    oidresp.add_extension(paperesp)
+  end
+
+  def render_response(oidresp)
+    if oidresp.needs_signing
+      signed_response = server.signatory.sign(oidresp)
+    end
+    web_response = server.encode_response(oidresp)
+
+    case web_response.code
+    when HTTP_OK
+      render :text => web_response.body, :status => 200
+
+    when HTTP_REDIRECT
+      redirect_to web_response.headers['location']
+
+    else
+      render :text => web_response.body, :status => 400
+    end
+  end
+
+
+end
diff --git a/vendor/gems/ruby-openid-2.1.4/examples/rails_openid/app/helpers/application_helper.rb b/vendor/gems/ruby-openid-2.1.4/examples/rails_openid/app/helpers/application_helper.rb
new file mode 100644 (file)
index 0000000..22a7940
--- /dev/null
@@ -0,0 +1,3 @@
+# Methods added to this helper will be available to all templates in the application.
+module ApplicationHelper
+end
diff --git a/vendor/gems/ruby-openid-2.1.4/examples/rails_openid/app/helpers/login_helper.rb b/vendor/gems/ruby-openid-2.1.4/examples/rails_openid/app/helpers/login_helper.rb
new file mode 100644 (file)
index 0000000..a0418e3
--- /dev/null
@@ -0,0 +1,2 @@
+module LoginHelper
+end
diff --git a/vendor/gems/ruby-openid-2.1.4/examples/rails_openid/app/helpers/server_helper.rb b/vendor/gems/ruby-openid-2.1.4/examples/rails_openid/app/helpers/server_helper.rb
new file mode 100644 (file)
index 0000000..409b210
--- /dev/null
@@ -0,0 +1,9 @@
+
+module ServerHelper
+
+  def url_for_user
+    url_for :controller => 'user', :action => session[:username]
+  end
+
+end
+
diff --git a/vendor/gems/ruby-openid-2.1.4/examples/rails_openid/app/views/consumer/index.rhtml b/vendor/gems/ruby-openid-2.1.4/examples/rails_openid/app/views/consumer/index.rhtml
new file mode 100644 (file)
index 0000000..0cecbe8
--- /dev/null
@@ -0,0 +1,81 @@
+<html>
+<head>
+<title>Rails OpenID Example Relying Party</title>
+</head>
+  <style type="text/css">
+      * {
+        font-family: verdana,sans-serif;
+      }
+      body {
+        width: 50em;
+        margin: 1em;
+      }
+      div {
+        padding: .5em;
+      }
+      .alert {
+        border: 1px solid #e7dc2b;
+        background: #fff888;
+      }
+      .error {
+        border: 1px solid #ff0000;
+        background: #ffaaaa;
+      }
+      .success {
+        border: 1px solid #00ff00;
+        background: #aaffaa;
+      }
+      #verify-form {
+        border: 1px solid #777777;
+        background: #dddddd;
+        margin-top: 1em;
+        padding-bottom: 0em;
+      }
+      input.openid {
+        background: url( /images/openid_login_bg.gif ) no-repeat;
+        background-position: 0 50%;
+        background-color: #fff;
+        padding-left: 18px;
+      }
+  </style>
+  <body>
+    <h1>Rails OpenID Example Relying Party</h1>
+    <% if flash[:alert] %>
+      <div class='alert'>
+       <%= h(flash[:alert]) %>
+      </div>
+    <% end %>
+    <% if flash[:error] %>
+      <div class='error'>
+       <%= h(flash[:error]) %>
+      </div>
+    <% end %>
+    <% if flash[:success] %>
+      <div class='success'>
+       <%= h(flash[:success]) %>
+      </div>
+    <% end %>
+    <% if flash[:sreg_results] %>
+      <div class='alert'>
+      <%= flash[:sreg_results] %>
+      </div>
+    <% end %>
+    <% if flash[:pape_results] %>
+      <div class='alert'>
+      <%= flash[:pape_results] %>
+      </div>
+    <% end %>
+    <div id="verify-form">
+      <form method="get" accept-charset="UTF-8" 
+            action='<%= url_for :action => 'start' %>'>
+        Identifier:
+        <input type="text" class="openid" name="openid_identifier" />
+        <input type="submit" value="Verify" /><br />
+        <input type="checkbox" name="immediate" id="immediate" /><label for="immediate">Use immediate mode</label><br/>
+        <input type="checkbox" name="use_sreg" id="use_sreg" /><label for="use_sreg">Request registration data</label><br/>
+        <input type="checkbox" name="use_pape" id="use_pape" /><label for="use_pape">Request phishing-resistent auth policy (PAPE)</label><br/>
+        <input type="checkbox" name="force_post" id="force_post" /><label for="force_post">Force the transaction to use POST by adding 2K of extra data</label>
+      </form>
+    </div>
+  </body>
+</html>
diff --git a/vendor/gems/ruby-openid-2.1.4/examples/rails_openid/app/views/layouts/server.rhtml b/vendor/gems/ruby-openid-2.1.4/examples/rails_openid/app/views/layouts/server.rhtml
new file mode 100644 (file)
index 0000000..3dd5d78
--- /dev/null
@@ -0,0 +1,68 @@
+<html>
+  <head><title>OpenID Server Example</title></head>
+  <style type="text/css">
+      * {
+        font-family: verdana,sans-serif;
+      }
+      body {
+        width: 50em;
+        margin: 1em;
+      }
+      div {
+        padding: .5em;
+      }
+      table {
+        margin: none;
+        padding: none;
+      }
+      .notice {
+        border: 1px solid #60964f;
+        background: #b3dca7;
+      }
+      .error {
+        border: 1px solid #ff0000;
+        background: #ffaaaa;
+      }
+      #login-form {
+        border: 1px solid #777777;
+        background: #dddddd;
+        margin-top: 1em;
+        padding-bottom: 0em;
+      }
+      table {
+        padding: 1em;
+      }
+      li {margin-bottom: .5em;}
+      span.openid:before {
+        content: url(<%= @base_url %>images/openid_login_bg.gif) ;
+      }
+      span.openid {
+        font-size: smaller;
+      }
+  </style>
+  <body>
+
+
+
+    <% if session[:username] %>
+      <div style="float:right;">
+        Welcome, <%= session[:username] %> | <%= link_to('Log out', :controller => 'login', :action => 'logout') %><br />
+       <span class="openid"><%= @base_url %>user/<%= session[:username] %></span>
+      </div>
+    <% end %>
+
+    <h3>Ruby OpenID Server Example</h3>
+
+    <hr/>
+
+    <% if flash[:notice] or flash[:error] %>
+     <div class="<%= flash[:notice].nil? ? 'error' : 'notice' %>">
+       <%= flash[:error] or flash[:notice] %>
+     </div>
+    <% end %>
+
+    <%= @content_for_layout %>
+
+
+  </body>
+</html>
diff --git a/vendor/gems/ruby-openid-2.1.4/examples/rails_openid/app/views/login/index.rhtml b/vendor/gems/ruby-openid-2.1.4/examples/rails_openid/app/views/login/index.rhtml
new file mode 100644 (file)
index 0000000..9990a57
--- /dev/null
@@ -0,0 +1,56 @@
+
+
+<% if session[:username].nil? %>
+
+<div id="login-form">
+  <form method="get" action="<%= url_for :controller => 'login', :action => 'submit' %>">
+Type a username: 
+  <input type="text" name="username" />
+  <input type="submit" value="Log In" />
+  </form>
+
+</div>
+
+<% end %>
+
+<p> Welcome to the Ruby OpenID example.  This code is a starting point
+for developers wishing to implement an OpenID provider or relying
+party.  We've used the <a href="http://rubyonrails.org/">Rails</a>
+platform to demonstrate, but the library code is not Rails specific.</p>
+
+<h2>To use the example provider</h2>
+<p>
+  <ol>
+
+    <li>Enter a username in the form above.  You will be "Logged In"
+    to the server, at which point you may authenticate using an OpenID
+    consumer. Your OpenID URL will be displayed after you log
+    in.<p>The server will automatically create an identity page for
+    you at <%= @base_url %>user/<i>name</i></p></li>
+
+    <li><p>Because WEBrick can only handle one thing at a time, you'll need to
+    run another instance of the example on another port if you want to use
+    a relying party to use with this example provider:</p>
+    <blockquote>
+    <code>script/server --port=3001</code>
+    </blockquote>
+
+    <p>(The RP needs to be able to access the provider, so unless you're
+    running this example on a public IP, you can't use the live example
+    at <a href="http://openidenabled.com/">openidenabled.com</a> on
+    your local provider.)</p>
+    </li>
+
+    <li>Point your browser to this new instance and follow the directions
+    below.</li>
+    <!-- Fun fact: 'url_for :port => 3001' doesn't work very well. -->
+  </ol>
+
+</p>
+
+<h2>To use the example relying party</h2>
+
+<p>Visit <a href="<%= url_for :controller => 'consumer' %>">/consumer</a>
+and enter your OpenID.</p>
+</p>
+
diff --git a/vendor/gems/ruby-openid-2.1.4/examples/rails_openid/app/views/server/decide.rhtml b/vendor/gems/ruby-openid-2.1.4/examples/rails_openid/app/views/server/decide.rhtml
new file mode 100644 (file)
index 0000000..5322b48
--- /dev/null
@@ -0,0 +1,26 @@
+<form method="post" action="<%= url_for :controller => 'server', :action => 'decision' %>">
+
+<table>
+  <tr><td>Site:</td><td><%= @oidreq.trust_root %></td></tr>
+
+  <% if @oidreq.id_select %>
+    <tr>
+      <td colspan="2">
+        You entered the server identifier at the relying party.
+        You'll need to send an identifier of your choosing.  Enter a
+        username below.
+      </td>
+    </tr>
+    <tr>
+      <td>Identity to send:</td>
+      <td><input type="text" name="id_to_send" size="25" /></td>
+    </tr>
+  <% else %>
+    <tr><td>Identity:</td><td><%= @oidreq.identity %></td></tr>
+  <% end %>
+</table>
+
+<input type="submit" name="yes" value="yes" />
+<input type="submit" name="no" value="no" />
+
+</form>
diff --git a/vendor/gems/ruby-openid-2.1.4/examples/rails_openid/config/boot.rb b/vendor/gems/ruby-openid-2.1.4/examples/rails_openid/config/boot.rb
new file mode 100644 (file)
index 0000000..9fcd50f
--- /dev/null
@@ -0,0 +1,19 @@
+# Don't change this file. Configuration is done in config/environment.rb and config/environments/*.rb
+
+unless defined?(RAILS_ROOT)
+  root_path = File.join(File.dirname(__FILE__), '..')
+  unless RUBY_PLATFORM =~ /mswin32/
+    require 'pathname'
+    root_path = Pathname.new(root_path).cleanpath(true).to_s
+  end
+  RAILS_ROOT = root_path
+end
+
+if File.directory?("#{RAILS_ROOT}/vendor/rails")
+  require "#{RAILS_ROOT}/vendor/rails/railties/lib/initializer"
+else
+  require 'rubygems'
+  require 'initializer'
+end
+
+Rails::Initializer.run(:set_load_path)
diff --git a/vendor/gems/ruby-openid-2.1.4/examples/rails_openid/config/environment.rb b/vendor/gems/ruby-openid-2.1.4/examples/rails_openid/config/environment.rb
new file mode 100644 (file)
index 0000000..0a78bf8
--- /dev/null
@@ -0,0 +1,54 @@
+# Be sure to restart your web server when you modify this file.
+
+# Uncomment below to force Rails into production mode when 
+# you don't control web/app server and can't set it the proper way
+# ENV['RAILS_ENV'] ||= 'production'
+
+# Bootstrap the Rails environment, frameworks, and default configuration
+require File.join(File.dirname(__FILE__), 'boot')
+
+Rails::Initializer.run do |config|
+  # Settings in config/environments/* take precedence those specified here
+  
+  # Skip frameworks you're not going to use
+  # config.frameworks -= [ :action_web_service, :action_mailer ]
+
+  # Add additional load paths for your own custom dirs
+  # config.load_paths += %W( #{RAILS_ROOT}/extras )
+
+  # Force all environments to use the same logger level 
+  # (by default production uses :info, the others :debug)
+  # config.log_level = :debug
+
+  # Use the database for sessions instead of the file system
+  # (create the session table with 'rake create_sessions_table')
+  # config.action_controller.session_store = :active_record_store
+
+  # Enable page/fragment caching by setting a file-based store
+  # (remember to create the caching directory and make it readable to the application)
+  # config.action_controller.fragment_cache_store = :file_store, "#{RAILS_ROOT}/cache"
+
+  # Activate observers that should always be running
+  # config.active_record.observers = :cacher, :garbage_collector
+
+  # Make Active Record use UTC-base instead of local time
+  # config.active_record.default_timezone = :utc
+  
+  # Use Active Record's schema dumper instead of SQL when creating the test database
+  # (enables use of different database adapters for development and test environments)
+  # config.active_record.schema_format = :ruby
+
+  # See Rails::Configuration for more options
+end
+
+# Add new inflection rules using the following format 
+# (all these examples are active by default):
+# Inflector.inflections do |inflect|
+#   inflect.plural /^(ox)$/i, '\1en'
+#   inflect.singular /^(ox)en/i, '\1'
+#   inflect.irregular 'person', 'people'
+#   inflect.uncountable %w( fish sheep )
+# end
+
+# Include your application configuration below
+ActionController::CgiRequest::DEFAULT_SESSION_OPTIONS[:session_key] = '_session_id_2'
diff --git a/vendor/gems/ruby-openid-2.1.4/examples/rails_openid/config/environments/development.rb b/vendor/gems/ruby-openid-2.1.4/examples/rails_openid/config/environments/development.rb
new file mode 100644 (file)
index 0000000..04b7792
--- /dev/null
@@ -0,0 +1,19 @@
+# Settings specified here will take precedence over those in config/environment.rb
+
+# In the development environment your application's code is reloaded on
+# every request.  This slows down response time but is perfect for development
+# since you don't have to restart the webserver when you make code changes.
+config.cache_classes     = false
+
+# Log error messages when you accidentally call methods on nil.
+config.whiny_nils        = true
+
+# Enable the breakpoint server that script/breakpointer connects to
+config.breakpoint_server = true
+
+# Show full error reports and disable caching
+config.action_controller.consider_all_requests_local = true
+config.action_controller.perform_caching             = false
+
+# Don't care if the mailer can't send
+config.action_mailer.raise_delivery_errors = false
diff --git a/vendor/gems/ruby-openid-2.1.4/examples/rails_openid/config/environments/production.rb b/vendor/gems/ruby-openid-2.1.4/examples/rails_openid/config/environments/production.rb
new file mode 100644 (file)
index 0000000..c9a4396
--- /dev/null
@@ -0,0 +1,19 @@
+# Settings specified here will take precedence over those in config/environment.rb
+
+# The production environment is meant for finished, "live" apps.
+# Code is not reloaded between requests
+config.cache_classes = true
+
+# Use a different logger for distributed setups
+# config.logger        = SyslogLogger.new
+
+
+# Full error reports are disabled and caching is turned on
+config.action_controller.consider_all_requests_local = false
+config.action_controller.perform_caching             = true
+
+# Enable serving of images, stylesheets, and javascripts from an asset server
+# config.action_controller.asset_host                  = "http://assets.example.com"
+
+# Disable delivery errors if you bad email addresses should just be ignored
+# config.action_mailer.raise_delivery_errors = false
diff --git a/vendor/gems/ruby-openid-2.1.4/examples/rails_openid/config/environments/test.rb b/vendor/gems/ruby-openid-2.1.4/examples/rails_openid/config/environments/test.rb
new file mode 100644 (file)
index 0000000..6a4cddb
--- /dev/null
@@ -0,0 +1,19 @@
+# Settings specified here will take precedence over those in config/environment.rb
+
+# The test environment is used exclusively to run your application's
+# test suite.  You never need to work with it otherwise.  Remember that
+# your test database is "scratch space" for the test suite and is wiped
+# and recreated between test runs.  Don't rely on the data there!
+config.cache_classes = true
+
+# Log error messages when you accidentally call methods on nil.
+config.whiny_nils    = true
+
+# Show full error reports and disable caching
+config.action_controller.consider_all_requests_local = true
+config.action_controller.perform_caching             = false
+
+# Tell ActionMailer not to deliver emails to the real world.
+# The :test delivery method accumulates sent emails in the
+# ActionMailer::Base.deliveries array.
+config.action_mailer.delivery_method = :test
\ No newline at end of file
diff --git a/vendor/gems/ruby-openid-2.1.4/examples/rails_openid/config/routes.rb b/vendor/gems/ruby-openid-2.1.4/examples/rails_openid/config/routes.rb
new file mode 100644 (file)
index 0000000..d5ceed2
--- /dev/null
@@ -0,0 +1,24 @@
+ActionController::Routing::Routes.draw do |map|
+  # Add your own custom routes here.
+  # The priority is based upon order of creation: first created -> highest priority.
+  
+  # Here's a sample route:
+  # map.connect 'products/:id', :controller => 'catalog', :action => 'view'
+  # Keep in mind you can assign values other than :controller and :action
+
+  # You can have the root of your site routed by hooking up '' 
+  # -- just remember to delete public/index.html.
+  # map.connect '', :controller => "welcome"
+
+  map.connect '', :controller => 'login'
+  map.connect 'server/xrds', :controller => 'server', :action => 'idp_xrds'
+  map.connect 'user/:username', :controller => 'server', :action => 'user_page'
+  map.connect 'user/:username/xrds', :controller => 'server', :action => 'user_xrds'
+
+  # Allow downloading Web Service WSDL as a file with an extension
+  # instead of a file named 'wsdl'
+  map.connect ':controller/service.wsdl', :action => 'wsdl'
+
+  # Install the default route as the lowest priority.
+  map.connect ':controller/:action/:id'
+end
diff --git a/vendor/gems/ruby-openid-2.1.4/examples/rails_openid/doc/README_FOR_APP b/vendor/gems/ruby-openid-2.1.4/examples/rails_openid/doc/README_FOR_APP
new file mode 100644 (file)
index 0000000..ac6c149
--- /dev/null
@@ -0,0 +1,2 @@
+Use this README file to introduce your application and point to useful places in the API for learning more.
+Run "rake appdoc" to generate API documentation for your models and controllers.
\ No newline at end of file
diff --git a/vendor/gems/ruby-openid-2.1.4/examples/rails_openid/public/404.html b/vendor/gems/ruby-openid-2.1.4/examples/rails_openid/public/404.html
new file mode 100644 (file)
index 0000000..0e18456
--- /dev/null
@@ -0,0 +1,8 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
+   "http://www.w3.org/TR/html4/loose.dtd">
+<html>
+<body>
+  <h1>File not found</h1>
+  <p>Change this error message for pages not found in public/404.html</p>
+</body>
+</html>
\ No newline at end of file
diff --git a/vendor/gems/ruby-openid-2.1.4/examples/rails_openid/public/500.html b/vendor/gems/ruby-openid-2.1.4/examples/rails_openid/public/500.html
new file mode 100644 (file)
index 0000000..a1001a0
--- /dev/null
@@ -0,0 +1,8 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
+   "http://www.w3.org/TR/html4/loose.dtd">
+<html>
+<body>
+  <h1>Application error (Apache)</h1>
+  <p>Change this error message for exceptions thrown outside of an action (like in Dispatcher setups or broken Ruby code) in public/500.html</p>
+</body>
+</html>
\ No newline at end of file
diff --git a/vendor/gems/ruby-openid-2.1.4/examples/rails_openid/public/dispatch.cgi b/vendor/gems/ruby-openid-2.1.4/examples/rails_openid/public/dispatch.cgi
new file mode 100644 (file)
index 0000000..dfe5dc3
--- /dev/null
@@ -0,0 +1,12 @@
+#!/usr/bin/ruby1.8
+
+#!/usr/local/bin/ruby
+
+require File.dirname(__FILE__) + "/../config/environment" unless defined?(RAILS_ROOT)
+
+# If you're using RubyGems and mod_ruby, this require should be changed to an absolute path one, like:
+# "/usr/local/lib/ruby/gems/1.8/gems/rails-0.8.0/lib/dispatcher" -- otherwise performance is severely impaired
+require "dispatcher"
+
+ADDITIONAL_LOAD_PATHS.reverse.each { |dir| $:.unshift(dir) if File.directory?(dir) } if defined?(Apache::RubyRun)
+Dispatcher.dispatch
\ No newline at end of file
diff --git a/vendor/gems/ruby-openid-2.1.4/examples/rails_openid/public/dispatch.fcgi b/vendor/gems/ruby-openid-2.1.4/examples/rails_openid/public/dispatch.fcgi
new file mode 100644 (file)
index 0000000..d02c35b
--- /dev/null
@@ -0,0 +1,26 @@
+#!/usr/bin/ruby1.8
+
+#!/usr/local/bin/ruby
+#
+# You may specify the path to the FastCGI crash log (a log of unhandled
+# exceptions which forced the FastCGI instance to exit, great for debugging)
+# and the number of requests to process before running garbage collection.
+#
+# By default, the FastCGI crash log is RAILS_ROOT/log/fastcgi.crash.log
+# and the GC period is nil (turned off).  A reasonable number of requests
+# could range from 10-100 depending on the memory footprint of your app.
+#
+# Example:
+#   # Default log path, normal GC behavior.
+#   RailsFCGIHandler.process!
+#
+#   # Default log path, 50 requests between GC.
+#   RailsFCGIHandler.process! nil, 50
+#
+#   # Custom log path, normal GC behavior.
+#   RailsFCGIHandler.process! '/var/log/myapp_fcgi_crash.log'
+#
+require File.dirname(__FILE__) + "/../config/environment"
+require 'fcgi_handler'
+
+RailsFCGIHandler.process!
diff --git a/vendor/gems/ruby-openid-2.1.4/examples/rails_openid/public/dispatch.rb b/vendor/gems/ruby-openid-2.1.4/examples/rails_openid/public/dispatch.rb
new file mode 100644 (file)
index 0000000..dfe5dc3
--- /dev/null
@@ -0,0 +1,12 @@
+#!/usr/bin/ruby1.8
+
+#!/usr/local/bin/ruby
+
+require File.dirname(__FILE__) + "/../config/environment" unless defined?(RAILS_ROOT)
+
+# If you're using RubyGems and mod_ruby, this require should be changed to an absolute path one, like:
+# "/usr/local/lib/ruby/gems/1.8/gems/rails-0.8.0/lib/dispatcher" -- otherwise performance is severely impaired
+require "dispatcher"
+
+ADDITIONAL_LOAD_PATHS.reverse.each { |dir| $:.unshift(dir) if File.directory?(dir) } if defined?(Apache::RubyRun)
+Dispatcher.dispatch
\ No newline at end of file
diff --git a/vendor/gems/ruby-openid-2.1.4/examples/rails_openid/public/favicon.ico b/vendor/gems/ruby-openid-2.1.4/examples/rails_openid/public/favicon.ico
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/vendor/gems/ruby-openid-2.1.4/examples/rails_openid/public/images/openid_login_bg.gif b/vendor/gems/ruby-openid-2.1.4/examples/rails_openid/public/images/openid_login_bg.gif
new file mode 100644 (file)
index 0000000..cde836c
Binary files /dev/null and b/vendor/gems/ruby-openid-2.1.4/examples/rails_openid/public/images/openid_login_bg.gif differ
diff --git a/vendor/gems/ruby-openid-2.1.4/examples/rails_openid/public/javascripts/controls.js b/vendor/gems/ruby-openid-2.1.4/examples/rails_openid/public/javascripts/controls.js
new file mode 100644 (file)
index 0000000..9742b69
--- /dev/null
@@ -0,0 +1,750 @@
+// Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
+//           (c) 2005 Ivan Krstic (http://blogs.law.harvard.edu/ivan)
+//           (c) 2005 Jon Tirsen (http://www.tirsen.com)
+// Contributors:
+//  Richard Livsey
+//  Rahul Bhargava
+//  Rob Wills
+// 
+// See scriptaculous.js for full license.
+
+// Autocompleter.Base handles all the autocompletion functionality 
+// that's independent of the data source for autocompletion. This
+// includes drawing the autocompletion menu, observing keyboard
+// and mouse events, and similar.
+//
+// Specific autocompleters need to provide, at the very least, 
+// a getUpdatedChoices function that will be invoked every time
+// the text inside the monitored textbox changes. This method 
+// should get the text for which to provide autocompletion by
+// invoking this.getToken(), NOT by directly accessing
+// this.element.value. This is to allow incremental tokenized
+// autocompletion. Specific auto-completion logic (AJAX, etc)
+// belongs in getUpdatedChoices.
+//
+// Tokenized incremental autocompletion is enabled automatically
+// when an autocompleter is instantiated with the 'tokens' option
+// in the options parameter, e.g.:
+// new Ajax.Autocompleter('id','upd', '/url/', { tokens: ',' });
+// will incrementally autocomplete with a comma as the token.
+// Additionally, ',' in the above example can be replaced with
+// a token array, e.g. { tokens: [',', '\n'] } which
+// enables autocompletion on multiple tokens. This is most 
+// useful when one of the tokens is \n (a newline), as it 
+// allows smart autocompletion after linebreaks.
+
+var Autocompleter = {}
+Autocompleter.Base = function() {};
+Autocompleter.Base.prototype = {
+  baseInitialize: function(element, update, options) {
+    this.element     = $(element); 
+    this.update      = $(update);  
+    this.hasFocus    = false; 
+    this.changed     = false; 
+    this.active      = false; 
+    this.index       = 0;     
+    this.entryCount  = 0;
+
+    if (this.setOptions)
+      this.setOptions(options);
+    else
+      this.options = options || {};
+
+    this.options.paramName    = this.options.paramName || this.element.name;
+    this.options.tokens       = this.options.tokens || [];
+    this.options.frequency    = this.options.frequency || 0.4;
+    this.options.minChars     = this.options.minChars || 1;
+    this.options.onShow       = this.options.onShow || 
+    function(element, update){ 
+      if(!update.style.position || update.style.position=='absolute') {
+        update.style.position = 'absolute';
+        Position.clone(element, update, {setHeight: false, offsetTop: element.offsetHeight});
+      }
+      Effect.Appear(update,{duration:0.15});
+    };
+    this.options.onHide = this.options.onHide || 
+    function(element, update){ new Effect.Fade(update,{duration:0.15}) };
+
+    if (typeof(this.options.tokens) == 'string') 
+      this.options.tokens = new Array(this.options.tokens);
+
+    this.observer = null;
+    
+    this.element.setAttribute('autocomplete','off');
+
+    Element.hide(this.update);
+
+    Event.observe(this.element, "blur", this.onBlur.bindAsEventListener(this));
+    Event.observe(this.element, "keypress", this.onKeyPress.bindAsEventListener(this));
+  },
+
+  show: function() {
+    if(Element.getStyle(this.update, 'display')=='none') this.options.onShow(this.element, this.update);
+    if(!this.iefix && 
+      (navigator.appVersion.indexOf('MSIE')>0) &&
+      (navigator.userAgent.indexOf('Opera')<0) &&
+      (Element.getStyle(this.update, 'position')=='absolute')) {
+      new Insertion.After(this.update, 
+       '<iframe id="' + this.update.id + '_iefix" '+
+       'style="display:none;position:absolute;filter:progid:DXImageTransform.Microsoft.Alpha(opacity=0);" ' +
+       'src="javascript:false;" frameborder="0" scrolling="no"></iframe>');
+      this.iefix = $(this.update.id+'_iefix');
+    }
+    if(this.iefix) setTimeout(this.fixIEOverlapping.bind(this), 50);
+  },
+  
+  fixIEOverlapping: function() {
+    Position.clone(this.update, this.iefix);
+    this.iefix.style.zIndex = 1;
+    this.update.style.zIndex = 2;
+    Element.show(this.iefix);
+  },
+
+  hide: function() {
+    this.stopIndicator();
+    if(Element.getStyle(this.update, 'display')!='none') this.options.onHide(this.element, this.update);
+    if(this.iefix) Element.hide(this.iefix);
+  },
+
+  startIndicator: function() {
+    if(this.options.indicator) Element.show(this.options.indicator);
+  },
+
+  stopIndicator: function() {
+    if(this.options.indicator) Element.hide(this.options.indicator);
+  },
+
+  onKeyPress: function(event) {
+    if(this.active)
+      switch(event.keyCode) {
+       case Event.KEY_TAB:
+       case Event.KEY_RETURN:
+         this.selectEntry();
+         Event.stop(event);
+       case Event.KEY_ESC:
+         this.hide();
+         this.active = false;
+         Event.stop(event);
+         return;
+       case Event.KEY_LEFT:
+       case Event.KEY_RIGHT:
+         return;
+       case Event.KEY_UP:
+         this.markPrevious();
+         this.render();
+         if(navigator.appVersion.indexOf('AppleWebKit')>0) Event.stop(event);
+         return;
+       case Event.KEY_DOWN:
+         this.markNext();
+         this.render();
+         if(navigator.appVersion.indexOf('AppleWebKit')>0) Event.stop(event);
+         return;
+      }
+     else 
+      if(event.keyCode==Event.KEY_TAB || event.keyCode==Event.KEY_RETURN) 
+        return;
+
+    this.changed = true;
+    this.hasFocus = true;
+
+    if(this.observer) clearTimeout(this.observer);
+      this.observer = 
+        setTimeout(this.onObserverEvent.bind(this), this.options.frequency*1000);
+  },
+
+  onHover: function(event) {
+    var element = Event.findElement(event, 'LI');
+    if(this.index != element.autocompleteIndex) 
+    {
+        this.index = element.autocompleteIndex;
+        this.render();
+    }
+    Event.stop(event);
+  },
+  
+  onClick: function(event) {
+    var element = Event.findElement(event, 'LI');
+    this.index = element.autocompleteIndex;
+    this.selectEntry();
+    this.hide();
+  },
+  
+  onBlur: function(event) {
+    // needed to make click events working
+    setTimeout(this.hide.bind(this), 250);
+    this.hasFocus = false;
+    this.active = false;     
+  }, 
+  
+  render: function() {
+    if(this.entryCount > 0) {
+      for (var i = 0; i < this.entryCount; i++)
+        this.index==i ? 
+          Element.addClassName(this.getEntry(i),"selected") : 
+          Element.removeClassName(this.getEntry(i),"selected");
+        
+      if(this.hasFocus) { 
+        this.show();
+        this.active = true;
+      }
+    } else {
+      this.active = false;
+      this.hide();
+    }
+  },
+  
+  markPrevious: function() {
+    if(this.index > 0) this.index--
+      else this.index = this.entryCount-1;
+  },
+  
+  markNext: function() {
+    if(this.index < this.entryCount-1) this.index++
+      else this.index = 0;
+  },
+  
+  getEntry: function(index) {
+    return this.update.firstChild.childNodes[index];
+  },
+  
+  getCurrentEntry: function() {
+    return this.getEntry(this.index);
+  },
+  
+  selectEntry: function() {
+    this.active = false;
+    this.updateElement(this.getCurrentEntry());
+  },
+
+  updateElement: function(selectedElement) {
+    if (this.options.updateElement) {
+      this.options.updateElement(selectedElement);
+      return;
+    }
+
+    var value = Element.collectTextNodesIgnoreClass(selectedElement, 'informal');
+    var lastTokenPos = this.findLastToken();
+    if (lastTokenPos != -1) {
+      var newValue = this.element.value.substr(0, lastTokenPos + 1);
+      var whitespace = this.element.value.substr(lastTokenPos + 1).match(/^\s+/);
+      if (whitespace)
+        newValue += whitespace[0];
+      this.element.value = newValue + value;
+    } else {
+      this.element.value = value;
+    }
+    this.element.focus();
+    
+    if (this.options.afterUpdateElement)
+      this.options.afterUpdateElement(this.element, selectedElement);
+  },
+
+  updateChoices: function(choices) {
+    if(!this.changed && this.hasFocus) {
+      this.update.innerHTML = choices;
+      Element.cleanWhitespace(this.update);
+      Element.cleanWhitespace(this.update.firstChild);
+
+      if(this.update.firstChild && this.update.firstChild.childNodes) {
+        this.entryCount = 
+          this.update.firstChild.childNodes.length;
+        for (var i = 0; i < this.entryCount; i++) {
+          var entry = this.getEntry(i);
+          entry.autocompleteIndex = i;
+          this.addObservers(entry);
+        }
+      } else { 
+        this.entryCount = 0;
+      }
+
+      this.stopIndicator();
+
+      this.index = 0;
+      this.render();
+    }
+  },
+
+  addObservers: function(element) {
+    Event.observe(element, "mouseover", this.onHover.bindAsEventListener(this));
+    Event.observe(element, "click", this.onClick.bindAsEventListener(this));
+  },
+
+  onObserverEvent: function() {
+    this.changed = false;   
+    if(this.getToken().length>=this.options.minChars) {
+      this.startIndicator();
+      this.getUpdatedChoices();
+    } else {
+      this.active = false;
+      this.hide();
+    }
+  },
+
+  getToken: function() {
+    var tokenPos = this.findLastToken();
+    if (tokenPos != -1)
+      var ret = this.element.value.substr(tokenPos + 1).replace(/^\s+/,'').replace(/\s+$/,'');
+    else
+      var ret = this.element.value;
+
+    return /\n/.test(ret) ? '' : ret;
+  },
+
+  findLastToken: function() {
+    var lastTokenPos = -1;
+
+    for (var i=0; i<this.options.tokens.length; i++) {
+      var thisTokenPos = this.element.value.lastIndexOf(this.options.tokens[i]);
+      if (thisTokenPos > lastTokenPos)
+        lastTokenPos = thisTokenPos;
+    }
+    return lastTokenPos;
+  }
+}
+
+Ajax.Autocompleter = Class.create();
+Object.extend(Object.extend(Ajax.Autocompleter.prototype, Autocompleter.Base.prototype), {
+  initialize: function(element, update, url, options) {
+         this.baseInitialize(element, update, options);
+    this.options.asynchronous  = true;
+    this.options.onComplete    = this.onComplete.bind(this);
+    this.options.defaultParams = this.options.parameters || null;
+    this.url                   = url;
+  },
+
+  getUpdatedChoices: function() {
+    entry = encodeURIComponent(this.options.paramName) + '=' + 
+      encodeURIComponent(this.getToken());
+
+    this.options.parameters = this.options.callback ?
+      this.options.callback(this.element, entry) : entry;
+
+    if(this.options.defaultParams) 
+      this.options.parameters += '&' + this.options.defaultParams;
+
+    new Ajax.Request(this.url, this.options);
+  },
+
+  onComplete: function(request) {
+    this.updateChoices(request.responseText);
+  }
+
+});
+
+// The local array autocompleter. Used when you'd prefer to
+// inject an array of autocompletion options into the page, rather
+// than sending out Ajax queries, which can be quite slow sometimes.
+//
+// The constructor takes four parameters. The first two are, as usual,
+// the id of the monitored textbox, and id of the autocompletion menu.
+// The third is the array you want to autocomplete from, and the fourth
+// is the options block.
+//
+// Extra local autocompletion options:
+// - choices - How many autocompletion choices to offer
+//
+// - partialSearch - If false, the autocompleter will match entered
+//                    text only at the beginning of strings in the 
+//                    autocomplete array. Defaults to true, which will
+//                    match text at the beginning of any *word* in the
+//                    strings in the autocomplete array. If you want to
+//                    search anywhere in the string, additionally set
+//                    the option fullSearch to true (default: off).
+//
+// - fullSsearch - Search anywhere in autocomplete array strings.
+//
+// - partialChars - How many characters to enter before triggering
+//                   a partial match (unlike minChars, which defines
+//                   how many characters are required to do any match
+//                   at all). Defaults to 2.
+//
+// - ignoreCase - Whether to ignore case when autocompleting.
+//                 Defaults to true.
+//
+// It's possible to pass in a custom function as the 'selector' 
+// option, if you prefer to write your own autocompletion logic.
+// In that case, the other options above will not apply unless
+// you support them.
+
+Autocompleter.Local = Class.create();
+Autocompleter.Local.prototype = Object.extend(new Autocompleter.Base(), {
+  initialize: function(element, update, array, options) {
+    this.baseInitialize(element, update, options);
+    this.options.array = array;
+  },
+
+  getUpdatedChoices: function() {
+    this.updateChoices(this.options.selector(this));
+  },
+
+  setOptions: function(options) {
+    this.options = Object.extend({
+      choices: 10,
+      partialSearch: true,
+      partialChars: 2,
+      ignoreCase: true,
+      fullSearch: false,
+      selector: function(instance) {
+        var ret       = []; // Beginning matches
+        var partial   = []; // Inside matches
+        var entry     = instance.getToken();
+        var count     = 0;
+
+        for (var i = 0; i < instance.options.array.length &&  
+          ret.length < instance.options.choices ; i++) { 
+
+          var elem = instance.options.array[i];
+          var foundPos = instance.options.ignoreCase ? 
+            elem.toLowerCase().indexOf(entry.toLowerCase()) : 
+            elem.indexOf(entry);
+
+          while (foundPos != -1) {
+            if (foundPos == 0 && elem.length != entry.length) { 
+              ret.push("<li><strong>" + elem.substr(0, entry.length) + "</strong>" + 
+                elem.substr(entry.length) + "</li>");
+              break;
+            } else if (entry.length >= instance.options.partialChars && 
+              instance.options.partialSearch && foundPos != -1) {
+              if (instance.options.fullSearch || /\s/.test(elem.substr(foundPos-1,1))) {
+                partial.push("<li>" + elem.substr(0, foundPos) + "<strong>" +
+                  elem.substr(foundPos, entry.length) + "</strong>" + elem.substr(
+                  foundPos + entry.length) + "</li>");
+                break;
+              }
+            }
+
+            foundPos = instance.options.ignoreCase ? 
+              elem.toLowerCase().indexOf(entry.toLowerCase(), foundPos + 1) : 
+              elem.indexOf(entry, foundPos + 1);
+
+          }
+        }
+        if (partial.length)
+          ret = ret.concat(partial.slice(0, instance.options.choices - ret.length))
+        return "<ul>" + ret.join('') + "</ul>";
+      }
+    }, options || {});
+  }
+});
+
+// AJAX in-place editor
+//
+// see documentation on http://wiki.script.aculo.us/scriptaculous/show/Ajax.InPlaceEditor
+
+// Use this if you notice weird scrolling problems on some browsers,
+// the DOM might be a bit confused when this gets called so do this
+// waits 1 ms (with setTimeout) until it does the activation
+Field.scrollFreeActivate = function(field) {
+  setTimeout(function() {
+    Field.activate(field);
+  }, 1);
+}
+
+Ajax.InPlaceEditor = Class.create();
+Ajax.InPlaceEditor.defaultHighlightColor = "#FFFF99";
+Ajax.InPlaceEditor.prototype = {
+  initialize: function(element, url, options) {
+    this.url = url;
+    this.element = $(element);
+
+    this.options = Object.extend({
+      okText: "ok",
+      cancelText: "cancel",
+      savingText: "Saving...",
+      clickToEditText: "Click to edit",
+      okText: "ok",
+      rows: 1,
+      onComplete: function(transport, element) {
+        new Effect.Highlight(element, {startcolor: this.options.highlightcolor});
+      },
+      onFailure: function(transport) {
+        alert("Error communicating with the server: " + transport.responseText.stripTags());
+      },
+      callback: function(form) {
+        return Form.serialize(form);
+      },
+      handleLineBreaks: true,
+      loadingText: 'Loading...',
+      savingClassName: 'inplaceeditor-saving',
+      loadingClassName: 'inplaceeditor-loading',
+      formClassName: 'inplaceeditor-form',
+      highlightcolor: Ajax.InPlaceEditor.defaultHighlightColor,
+      highlightendcolor: "#FFFFFF",
+      externalControl: null,
+      ajaxOptions: {}
+    }, options || {});
+
+    if(!this.options.formId && this.element.id) {
+      this.options.formId = this.element.id + "-inplaceeditor";
+      if ($(this.options.formId)) {
+        // there's already a form with that name, don't specify an id
+        this.options.formId = null;
+      }
+    }
+    
+    if (this.options.externalControl) {
+      this.options.externalControl = $(this.options.externalControl);
+    }
+    
+    this.originalBackground = Element.getStyle(this.element, 'background-color');
+    if (!this.originalBackground) {
+      this.originalBackground = "transparent";
+    }
+    
+    this.element.title = this.options.clickToEditText;
+    
+    this.onclickListener = this.enterEditMode.bindAsEventListener(this);
+    this.mouseoverListener = this.enterHover.bindAsEventListener(this);
+    this.mouseoutListener = this.leaveHover.bindAsEventListener(this);
+    Event.observe(this.element, 'click', this.onclickListener);
+    Event.observe(this.element, 'mouseover', this.mouseoverListener);
+    Event.observe(this.element, 'mouseout', this.mouseoutListener);
+    if (this.options.externalControl) {
+      Event.observe(this.options.externalControl, 'click', this.onclickListener);
+      Event.observe(this.options.externalControl, 'mouseover', this.mouseoverListener);
+      Event.observe(this.options.externalControl, 'mouseout', this.mouseoutListener);
+    }
+  },
+  enterEditMode: function(evt) {
+    if (this.saving) return;
+    if (this.editing) return;
+    this.editing = true;
+    this.onEnterEditMode();
+    if (this.options.externalControl) {
+      Element.hide(this.options.externalControl);
+    }
+    Element.hide(this.element);
+    this.createForm();
+    this.element.parentNode.insertBefore(this.form, this.element);
+    Field.scrollFreeActivate(this.editField);
+    // stop the event to avoid a page refresh in Safari
+    if (evt) {
+      Event.stop(evt);
+    }
+    return false;
+  },
+  createForm: function() {
+    this.form = document.createElement("form");
+    this.form.id = this.options.formId;
+    Element.addClassName(this.form, this.options.formClassName)
+    this.form.onsubmit = this.onSubmit.bind(this);
+
+    this.createEditField();
+
+    if (this.options.textarea) {
+      var br = document.createElement("br");
+      this.form.appendChild(br);
+    }
+
+    okButton = document.createElement("input");
+    okButton.type = "submit";
+    okButton.value = this.options.okText;
+    this.form.appendChild(okButton);
+
+    cancelLink = document.createElement("a");
+    cancelLink.href = "#";
+    cancelLink.appendChild(document.createTextNode(this.options.cancelText));
+    cancelLink.onclick = this.onclickCancel.bind(this);
+    this.form.appendChild(cancelLink);
+  },
+  hasHTMLLineBreaks: function(string) {
+    if (!this.options.handleLineBreaks) return false;
+    return string.match(/<br/i) || string.match(/<p>/i);
+  },
+  convertHTMLLineBreaks: function(string) {
+    return string.replace(/<br>/gi, "\n").replace(/<br\/>/gi, "\n").replace(/<\/p>/gi, "\n").replace(/<p>/gi, "");
+  },
+  createEditField: function() {
+    var text;
+    if(this.options.loadTextURL) {
+      text = this.options.loadingText;
+    } else {
+      text = this.getText();
+    }
+    
+    if (this.options.rows == 1 && !this.hasHTMLLineBreaks(text)) {
+      this.options.textarea = false;
+      var textField = document.createElement("input");
+      textField.type = "text";
+      textField.name = "value";
+      textField.value = text;
+      textField.style.backgroundColor = this.options.highlightcolor;
+      var size = this.options.size || this.options.cols || 0;
+      if (size != 0) textField.size = size;
+      this.editField = textField;
+    } else {
+      this.options.textarea = true;
+      var textArea = document.createElement("textarea");
+      textArea.name = "value";
+      textArea.value = this.convertHTMLLineBreaks(text);
+      textArea.rows = this.options.rows;
+      textArea.cols = this.options.cols || 40;
+      this.editField = textArea;
+    }
+    
+    if(this.options.loadTextURL) {
+      this.loadExternalText();
+    }
+    this.form.appendChild(this.editField);
+  },
+  getText: function() {
+    return this.element.innerHTML;
+  },
+  loadExternalText: function() {
+    Element.addClassName(this.form, this.options.loadingClassName);
+    this.editField.disabled = true;
+    new Ajax.Request(
+      this.options.loadTextURL,
+      Object.extend({
+        asynchronous: true,
+        onComplete: this.onLoadedExternalText.bind(this)
+      }, this.options.ajaxOptions)
+    );
+  },
+  onLoadedExternalText: function(transport) {
+    Element.removeClassName(this.form, this.options.loadingClassName);
+    this.editField.disabled = false;
+    this.editField.value = transport.responseText.stripTags();
+  },
+  onclickCancel: function() {
+    this.onComplete();
+    this.leaveEditMode();
+    return false;
+  },
+  onFailure: function(transport) {
+    this.options.onFailure(transport);
+    if (this.oldInnerHTML) {
+      this.element.innerHTML = this.oldInnerHTML;
+      this.oldInnerHTML = null;
+    }
+    return false;
+  },
+  onSubmit: function() {
+    // onLoading resets these so we need to save them away for the Ajax call
+    var form = this.form;
+    var value = this.editField.value;
+    
+    // do this first, sometimes the ajax call returns before we get a chance to switch on Saving...
+    // which means this will actually switch on Saving... *after* we've left edit mode causing Saving...
+    // to be displayed indefinitely
+    this.onLoading();
+    
+    new Ajax.Updater(
+      { 
+        success: this.element,
+         // don't update on failure (this could be an option)
+        failure: null
+      },
+      this.url,
+      Object.extend({
+        parameters: this.options.callback(form, value),
+        onComplete: this.onComplete.bind(this),
+        onFailure: this.onFailure.bind(this)
+      }, this.options.ajaxOptions)
+    );
+    // stop the event to avoid a page refresh in Safari
+    if (arguments.length > 1) {
+      Event.stop(arguments[0]);
+    }
+    return false;
+  },
+  onLoading: function() {
+    this.saving = true;
+    this.removeForm();
+    this.leaveHover();
+    this.showSaving();
+  },
+  showSaving: function() {
+    this.oldInnerHTML = this.element.innerHTML;
+    this.element.innerHTML = this.options.savingText;
+    Element.addClassName(this.element, this.options.savingClassName);
+    this.element.style.backgroundColor = this.originalBackground;
+    Element.show(this.element);
+  },
+  removeForm: function() {
+    if(this.form) {
+      if (this.form.parentNode) Element.remove(this.form);
+      this.form = null;
+    }
+  },
+  enterHover: function() {
+    if (this.saving) return;
+    this.element.style.backgroundColor = this.options.highlightcolor;
+    if (this.effect) {
+      this.effect.cancel();
+    }
+    Element.addClassName(this.element, this.options.hoverClassName)
+  },
+  leaveHover: function() {
+    if (this.options.backgroundColor) {
+      this.element.style.backgroundColor = this.oldBackground;
+    }
+    Element.removeClassName(this.element, this.options.hoverClassName)
+    if (this.saving) return;
+    this.effect = new Effect.Highlight(this.element, {
+      startcolor: this.options.highlightcolor,
+      endcolor: this.options.highlightendcolor,
+      restorecolor: this.originalBackground
+    });
+  },
+  leaveEditMode: function() {
+    Element.removeClassName(this.element, this.options.savingClassName);
+    this.removeForm();
+    this.leaveHover();
+    this.element.style.backgroundColor = this.originalBackground;
+    Element.show(this.element);
+    if (this.options.externalControl) {
+      Element.show(this.options.externalControl);
+    }
+    this.editing = false;
+    this.saving = false;
+    this.oldInnerHTML = null;
+    this.onLeaveEditMode();
+  },
+  onComplete: function(transport) {
+    this.leaveEditMode();
+    this.options.onComplete.bind(this)(transport, this.element);
+  },
+  onEnterEditMode: function() {},
+  onLeaveEditMode: function() {},
+  dispose: function() {
+    if (this.oldInnerHTML) {
+      this.element.innerHTML = this.oldInnerHTML;
+    }
+    this.leaveEditMode();
+    Event.stopObserving(this.element, 'click', this.onclickListener);
+    Event.stopObserving(this.element, 'mouseover', this.mouseoverListener);
+    Event.stopObserving(this.element, 'mouseout', this.mouseoutListener);
+    if (this.options.externalControl) {
+      Event.stopObserving(this.options.externalControl, 'click', this.onclickListener);
+      Event.stopObserving(this.options.externalControl, 'mouseover', this.mouseoverListener);
+      Event.stopObserving(this.options.externalControl, 'mouseout', this.mouseoutListener);
+    }
+  }
+};
+
+// Delayed observer, like Form.Element.Observer, 
+// but waits for delay after last key input
+// Ideal for live-search fields
+
+Form.Element.DelayedObserver = Class.create();
+Form.Element.DelayedObserver.prototype = {
+  initialize: function(element, delay, callback) {
+    this.delay     = delay || 0.5;
+    this.element   = $(element);
+    this.callback  = callback;
+    this.timer     = null;
+    this.lastValue = $F(this.element); 
+    Event.observe(this.element,'keyup',this.delayedListener.bindAsEventListener(this));
+  },
+  delayedListener: function(event) {
+    if(this.lastValue == $F(this.element)) return;
+    if(this.timer) clearTimeout(this.timer);
+    this.timer = setTimeout(this.onTimerEvent.bind(this), this.delay * 1000);
+    this.lastValue = $F(this.element);
+  },
+  onTimerEvent: function() {
+    this.timer = null;
+    this.callback(this.element, $F(this.element));
+  }
+};
\ No newline at end of file
diff --git a/vendor/gems/ruby-openid-2.1.4/examples/rails_openid/public/javascripts/dragdrop.js b/vendor/gems/ruby-openid-2.1.4/examples/rails_openid/public/javascripts/dragdrop.js
new file mode 100644 (file)
index 0000000..92d1f73
--- /dev/null
@@ -0,0 +1,584 @@
+// Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
+// 
+// See scriptaculous.js for full license.
+
+/*--------------------------------------------------------------------------*/
+
+var Droppables = {
+  drops: [],
+
+  remove: function(element) {
+    this.drops = this.drops.reject(function(d) { return d.element==$(element) });
+  },
+
+  add: function(element) {
+    element = $(element);
+    var options = Object.extend({
+      greedy:     true,
+      hoverclass: null  
+    }, arguments[1] || {});
+
+    // cache containers
+    if(options.containment) {
+      options._containers = [];
+      var containment = options.containment;
+      if((typeof containment == 'object') && 
+        (containment.constructor == Array)) {
+        containment.each( function(c) { options._containers.push($(c)) });
+      } else {
+        options._containers.push($(containment));
+      }
+    }
+    
+    if(options.accept) options.accept = [options.accept].flatten();
+
+    Element.makePositioned(element); // fix IE
+    options.element = element;
+
+    this.drops.push(options);
+  },
+
+  isContained: function(element, drop) {
+    var parentNode = element.parentNode;
+    return drop._containers.detect(function(c) { return parentNode == c });
+  },
+
+  isAffected: function(point, element, drop) {
+    return (
+      (drop.element!=element) &&
+      ((!drop._containers) ||
+        this.isContained(element, drop)) &&
+      ((!drop.accept) ||
+        (Element.classNames(element).detect( 
+          function(v) { return drop.accept.include(v) } ) )) &&
+      Position.within(drop.element, point[0], point[1]) );
+  },
+
+  deactivate: function(drop) {
+    if(drop.hoverclass)
+      Element.removeClassName(drop.element, drop.hoverclass);
+    this.last_active = null;
+  },
+
+  activate: function(drop) {
+    if(drop.hoverclass)
+      Element.addClassName(drop.element, drop.hoverclass);
+    this.last_active = drop;
+  },
+
+  show: function(point, element) {
+    if(!this.drops.length) return;
+    
+    if(this.last_active) this.deactivate(this.last_active);
+    this.drops.each( function(drop) {
+      if(Droppables.isAffected(point, element, drop)) {
+        if(drop.onHover)
+           drop.onHover(element, drop.element, Position.overlap(drop.overlap, drop.element));
+        if(drop.greedy) { 
+          Droppables.activate(drop);
+          throw $break;
+        }
+      }
+    });
+  },
+
+  fire: function(event, element) {
+    if(!this.last_active) return;
+    Position.prepare();
+
+    if (this.isAffected([Event.pointerX(event), Event.pointerY(event)], element, this.last_active))
+      if (this.last_active.onDrop) 
+        this.last_active.onDrop(element, this.last_active.element, event);
+  },
+
+  reset: function() {
+    if(this.last_active)
+      this.deactivate(this.last_active);
+  }
+}
+
+var Draggables = {
+  drags: [],
+  observers: [],
+  
+  register: function(draggable) {
+    if(this.drags.length == 0) {
+      this.eventMouseUp   = this.endDrag.bindAsEventListener(this);
+      this.eventMouseMove = this.updateDrag.bindAsEventListener(this);
+      this.eventKeypress  = this.keyPress.bindAsEventListener(this);
+      
+      Event.observe(document, "mouseup", this.eventMouseUp);
+      Event.observe(document, "mousemove", this.eventMouseMove);
+      Event.observe(document, "keypress", this.eventKeypress);
+    }
+    this.drags.push(draggable);
+  },
+  
+  unregister: function(draggable) {
+    this.drags = this.drags.reject(function(d) { return d==draggable });
+    if(this.drags.length == 0) {
+      Event.stopObserving(document, "mouseup", this.eventMouseUp);
+      Event.stopObserving(document, "mousemove", this.eventMouseMove);
+      Event.stopObserving(document, "keypress", this.eventKeypress);
+    }
+  },
+  
+  activate: function(draggable) {
+    window.focus(); // allows keypress events if window isn't currently focused, fails for Safari
+    this.activeDraggable = draggable;
+  },
+  
+  deactivate: function(draggbale) {
+    this.activeDraggable = null;
+  },
+  
+  updateDrag: function(event) {
+    if(!this.activeDraggable) return;
+    var pointer = [Event.pointerX(event), Event.pointerY(event)];
+    // Mozilla-based browsers fire successive mousemove events with
+    // the same coordinates, prevent needless redrawing (moz bug?)
+    if(this._lastPointer && (this._lastPointer.inspect() == pointer.inspect())) return;
+    this._lastPointer = pointer;
+    this.activeDraggable.updateDrag(event, pointer);
+  },
+  
+  endDrag: function(event) {
+    if(!this.activeDraggable) return;
+    this._lastPointer = null;
+    this.activeDraggable.endDrag(event);
+  },
+  
+  keyPress: function(event) {
+    if(this.activeDraggable)
+      this.activeDraggable.keyPress(event);
+  },
+  
+  addObserver: function(observer) {
+    this.observers.push(observer);
+    this._cacheObserverCallbacks();
+  },
+  
+  removeObserver: function(element) {  // element instead of observer fixes mem leaks
+    this.observers = this.observers.reject( function(o) { return o.element==element });
+    this._cacheObserverCallbacks();
+  },
+  
+  notify: function(eventName, draggable, event) {  // 'onStart', 'onEnd', 'onDrag'
+    if(this[eventName+'Count'] > 0)
+      this.observers.each( function(o) {
+        if(o[eventName]) o[eventName](eventName, draggable, event);
+      });
+  },
+  
+  _cacheObserverCallbacks: function() {
+    ['onStart','onEnd','onDrag'].each( function(eventName) {
+      Draggables[eventName+'Count'] = Draggables.observers.select(
+        function(o) { return o[eventName]; }
+      ).length;
+    });
+  }
+}
+
+/*--------------------------------------------------------------------------*/
+
+var Draggable = Class.create();
+Draggable.prototype = {
+  initialize: function(element) {
+    var options = Object.extend({
+      handle: false,
+      starteffect: function(element) { 
+        new Effect.Opacity(element, {duration:0.2, from:1.0, to:0.7}); 
+      },
+      reverteffect: function(element, top_offset, left_offset) {
+        var dur = Math.sqrt(Math.abs(top_offset^2)+Math.abs(left_offset^2))*0.02;
+        element._revert = new Effect.MoveBy(element, -top_offset, -left_offset, {duration:dur});
+      },
+      endeffect: function(element) { 
+        new Effect.Opacity(element, {duration:0.2, from:0.7, to:1.0}); 
+      },
+      zindex: 1000,
+      revert: false,
+      snap: false   // false, or xy or [x,y] or function(x,y){ return [x,y] }
+    }, arguments[1] || {});
+
+    this.element = $(element);
+    
+    if(options.handle && (typeof options.handle == 'string'))
+      this.handle = Element.childrenWithClassName(this.element, options.handle)[0];  
+    if(!this.handle) this.handle = $(options.handle);
+    if(!this.handle) this.handle = this.element;
+
+    Element.makePositioned(this.element); // fix IE    
+
+    this.delta    = this.currentDelta();
+    this.options  = options;
+    this.dragging = false;   
+
+    this.eventMouseDown = this.initDrag.bindAsEventListener(this);
+    Event.observe(this.handle, "mousedown", this.eventMouseDown);
+    
+    Draggables.register(this);
+  },
+  
+  destroy: function() {
+    Event.stopObserving(this.handle, "mousedown", this.eventMouseDown);
+    Draggables.unregister(this);
+  },
+  
+  currentDelta: function() {
+    return([
+      parseInt(this.element.style.left || '0'),
+      parseInt(this.element.style.top || '0')]);
+  },
+  
+  initDrag: function(event) {
+    if(Event.isLeftClick(event)) {    
+      // abort on form elements, fixes a Firefox issue
+      var src = Event.element(event);
+      if(src.tagName && (
+        src.tagName=='INPUT' ||
+        src.tagName=='SELECT' ||
+        src.tagName=='BUTTON' ||
+        src.tagName=='TEXTAREA')) return;
+        
+      if(this.element._revert) {
+        this.element._revert.cancel();
+        this.element._revert = null;
+      }
+      
+      var pointer = [Event.pointerX(event), Event.pointerY(event)];
+      var pos     = Position.cumulativeOffset(this.element);
+      this.offset = [0,1].map( function(i) { return (pointer[i] - pos[i]) });
+      
+      Draggables.activate(this);
+      Event.stop(event);
+    }
+  },
+  
+  startDrag: function(event) {
+    this.dragging = true;
+    
+    if(this.options.zindex) {
+      this.originalZ = parseInt(Element.getStyle(this.element,'z-index') || 0);
+      this.element.style.zIndex = this.options.zindex;
+    }
+    
+    if(this.options.ghosting) {
+      this._clone = this.element.cloneNode(true);
+      Position.absolutize(this.element);
+      this.element.parentNode.insertBefore(this._clone, this.element);
+    }
+    
+    Draggables.notify('onStart', this, event);
+    if(this.options.starteffect) this.options.starteffect(this.element);
+  },
+  
+  updateDrag: function(event, pointer) {
+    if(!this.dragging) this.startDrag(event);
+    Position.prepare();
+    Droppables.show(pointer, this.element);
+    Draggables.notify('onDrag', this, event);
+    this.draw(pointer);
+    if(this.options.change) this.options.change(this);
+    
+    // fix AppleWebKit rendering
+    if(navigator.appVersion.indexOf('AppleWebKit')>0) window.scrollBy(0,0);
+    Event.stop(event);
+  },
+  
+  finishDrag: function(event, success) {
+    this.dragging = false;
+
+    if(this.options.ghosting) {
+      Position.relativize(this.element);
+      Element.remove(this._clone);
+      this._clone = null;
+    }
+
+    if(success) Droppables.fire(event, this.element);
+    Draggables.notify('onEnd', this, event);
+
+    var revert = this.options.revert;
+    if(revert && typeof revert == 'function') revert = revert(this.element);
+    
+    var d = this.currentDelta();
+    if(revert && this.options.reverteffect) {
+      this.options.reverteffect(this.element, 
+        d[1]-this.delta[1], d[0]-this.delta[0]);
+    } else {
+      this.delta = d;
+    }
+
+    if(this.options.zindex)
+      this.element.style.zIndex = this.originalZ;
+
+    if(this.options.endeffect) 
+      this.options.endeffect(this.element);
+
+    Draggables.deactivate(this);
+    Droppables.reset();
+  },
+  
+  keyPress: function(event) {
+    if(!event.keyCode==Event.KEY_ESC) return;
+    this.finishDrag(event, false);
+    Event.stop(event);
+  },
+  
+  endDrag: function(event) {
+    if(!this.dragging) return;
+    this.finishDrag(event, true);
+    Event.stop(event);
+  },
+  
+  draw: function(point) {
+    var pos = Position.cumulativeOffset(this.element);
+    var d = this.currentDelta();
+    pos[0] -= d[0]; pos[1] -= d[1];
+    
+    var p = [0,1].map(function(i){ return (point[i]-pos[i]-this.offset[i]) }.bind(this));
+    
+    if(this.options.snap) {
+      if(typeof this.options.snap == 'function') {
+        p = this.options.snap(p[0],p[1]);
+      } else {
+      if(this.options.snap instanceof Array) {
+        p = p.map( function(v, i) {
+          return Math.round(v/this.options.snap[i])*this.options.snap[i] }.bind(this))
+      } else {
+        p = p.map( function(v) {
+          return Math.round(v/this.options.snap)*this.options.snap }.bind(this))
+      }
+    }}
+    
+    var style = this.element.style;
+    if((!this.options.constraint) || (this.options.constraint=='horizontal'))
+      style.left = p[0] + "px";
+    if((!this.options.constraint) || (this.options.constraint=='vertical'))
+      style.top  = p[1] + "px";
+    if(style.visibility=="hidden") style.visibility = ""; // fix gecko rendering
+  }
+}
+
+/*--------------------------------------------------------------------------*/
+
+var SortableObserver = Class.create();
+SortableObserver.prototype = {
+  initialize: function(element, observer) {
+    this.element   = $(element);
+    this.observer  = observer;
+    this.lastValue = Sortable.serialize(this.element);
+  },
+  
+  onStart: function() {
+    this.lastValue = Sortable.serialize(this.element);
+  },
+  
+  onEnd: function() {
+    Sortable.unmark();
+    if(this.lastValue != Sortable.serialize(this.element))
+      this.observer(this.element)
+  }
+}
+
+var Sortable = {
+  sortables: new Array(),
+  
+  options: function(element){
+    element = $(element);
+    return this.sortables.detect(function(s) { return s.element == element });
+  },
+  
+  destroy: function(element){
+    element = $(element);
+    this.sortables.findAll(function(s) { return s.element == element }).each(function(s){
+      Draggables.removeObserver(s.element);
+      s.droppables.each(function(d){ Droppables.remove(d) });
+      s.draggables.invoke('destroy');
+    });
+    this.sortables = this.sortables.reject(function(s) { return s.element == element });
+  },
+  
+  create: function(element) {
+    element = $(element);
+    var options = Object.extend({ 
+      element:     element,
+      tag:         'li',       // assumes li children, override with tag: 'tagname'
+      dropOnEmpty: false,
+      tree:        false,      // fixme: unimplemented
+      overlap:     'vertical', // one of 'vertical', 'horizontal'
+      constraint:  'vertical', // one of 'vertical', 'horizontal', false
+      containment: element,    // also takes array of elements (or id's); or false
+      handle:      false,      // or a CSS class
+      only:        false,
+      hoverclass:  null,
+      ghosting:    false,
+      format:      null,
+      onChange:    Prototype.emptyFunction,
+      onUpdate:    Prototype.emptyFunction
+    }, arguments[1] || {});
+
+    // clear any old sortable with same element
+    this.destroy(element);
+
+    // build options for the draggables
+    var options_for_draggable = {
+      revert:      true,
+      ghosting:    options.ghosting,
+      constraint:  options.constraint,
+      handle:      options.handle };
+
+    if(options.starteffect)
+      options_for_draggable.starteffect = options.starteffect;
+
+    if(options.reverteffect)
+      options_for_draggable.reverteffect = options.reverteffect;
+    else
+      if(options.ghosting) options_for_draggable.reverteffect = function(element) {
+        element.style.top  = 0;
+        element.style.left = 0;
+      };
+
+    if(options.endeffect)
+      options_for_draggable.endeffect = options.endeffect;
+
+    if(options.zindex)
+      options_for_draggable.zindex = options.zindex;
+
+    // build options for the droppables  
+    var options_for_droppable = {
+      overlap:     options.overlap,
+      containment: options.containment,
+      hoverclass:  options.hoverclass,
+      onHover:     Sortable.onHover,
+      greedy:      !options.dropOnEmpty
+    }
+
+    // fix for gecko engine
+    Element.cleanWhitespace(element); 
+
+    options.draggables = [];
+    options.droppables = [];
+
+    // make it so
+
+    // drop on empty handling
+    if(options.dropOnEmpty) {
+      Droppables.add(element,
+        {containment: options.containment, onHover: Sortable.onEmptyHover, greedy: false});
+      options.droppables.push(element);
+    }
+
+    (this.findElements(element, options) || []).each( function(e) {
+      // handles are per-draggable
+      var handle = options.handle ? 
+        Element.childrenWithClassName(e, options.handle)[0] : e;    
+      options.draggables.push(
+        new Draggable(e, Object.extend(options_for_draggable, { handle: handle })));
+      Droppables.add(e, options_for_droppable);
+      options.droppables.push(e);      
+    });
+
+    // keep reference
+    this.sortables.push(options);
+
+    // for onupdate
+    Draggables.addObserver(new SortableObserver(element, options.onUpdate));
+
+  },
+
+  // return all suitable-for-sortable elements in a guaranteed order
+  findElements: function(element, options) {
+    if(!element.hasChildNodes()) return null;
+    var elements = [];
+    $A(element.childNodes).each( function(e) {
+      if(e.tagName && e.tagName.toUpperCase()==options.tag.toUpperCase() &&
+        (!options.only || (Element.hasClassName(e, options.only))))
+          elements.push(e);
+      if(options.tree) {
+        var grandchildren = this.findElements(e, options);
+        if(grandchildren) elements.push(grandchildren);
+      }
+    });
+
+    return (elements.length>0 ? elements.flatten() : null);
+  },
+
+  onHover: function(element, dropon, overlap) {
+    if(overlap>0.5) {
+      Sortable.mark(dropon, 'before');
+      if(dropon.previousSibling != element) {
+        var oldParentNode = element.parentNode;
+        element.style.visibility = "hidden"; // fix gecko rendering
+        dropon.parentNode.insertBefore(element, dropon);
+        if(dropon.parentNode!=oldParentNode) 
+          Sortable.options(oldParentNode).onChange(element);
+        Sortable.options(dropon.parentNode).onChange(element);
+      }
+    } else {
+      Sortable.mark(dropon, 'after');
+      var nextElement = dropon.nextSibling || null;
+      if(nextElement != element) {
+        var oldParentNode = element.parentNode;
+        element.style.visibility = "hidden"; // fix gecko rendering
+        dropon.parentNode.insertBefore(element, nextElement);
+        if(dropon.parentNode!=oldParentNode) 
+          Sortable.options(oldParentNode).onChange(element);
+        Sortable.options(dropon.parentNode).onChange(element);
+      }
+    }
+  },
+
+  onEmptyHover: function(element, dropon) {
+    if(element.parentNode!=dropon) {
+      var oldParentNode = element.parentNode;
+      dropon.appendChild(element);
+      Sortable.options(oldParentNode).onChange(element);
+      Sortable.options(dropon).onChange(element);
+    }
+  },
+
+  unmark: function() {
+    if(Sortable._marker) Element.hide(Sortable._marker);
+  },
+
+  mark: function(dropon, position) {
+    // mark on ghosting only
+    var sortable = Sortable.options(dropon.parentNode);
+    if(sortable && !sortable.ghosting) return; 
+
+    if(!Sortable._marker) {
+      Sortable._marker = $('dropmarker') || document.createElement('DIV');
+      Element.hide(Sortable._marker);
+      Element.addClassName(Sortable._marker, 'dropmarker');
+      Sortable._marker.style.position = 'absolute';
+      document.getElementsByTagName("body").item(0).appendChild(Sortable._marker);
+    }    
+    var offsets = Position.cumulativeOffset(dropon);
+    Sortable._marker.style.left = offsets[0] + 'px';
+    Sortable._marker.style.top = offsets[1] + 'px';
+    
+    if(position=='after')
+      if(sortable.overlap == 'horizontal') 
+        Sortable._marker.style.left = (offsets[0]+dropon.clientWidth) + 'px';
+      else
+        Sortable._marker.style.top = (offsets[1]+dropon.clientHeight) + 'px';
+    
+    Element.show(Sortable._marker);
+  },
+
+  serialize: function(element) {
+    element = $(element);
+    var sortableOptions = this.options(element);
+    var options = Object.extend({
+      tag:  sortableOptions.tag,
+      only: sortableOptions.only,
+      name: element.id,
+      format: sortableOptions.format || /^[^_]*_(.*)$/
+    }, arguments[1] || {});
+    return $(this.findElements(element, options) || []).map( function(item) {
+      return (encodeURIComponent(options.name) + "[]=" + 
+              encodeURIComponent(item.id.match(options.format) ? item.id.match(options.format)[1] : ''));
+    }).join("&");
+  }
+}
\ No newline at end of file
diff --git a/vendor/gems/ruby-openid-2.1.4/examples/rails_openid/public/javascripts/effects.js b/vendor/gems/ruby-openid-2.1.4/examples/rails_openid/public/javascripts/effects.js
new file mode 100644 (file)
index 0000000..414398c
--- /dev/null
@@ -0,0 +1,854 @@
+// Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
+// Contributors:
+//  Justin Palmer (http://encytemedia.com/)
+//  Mark Pilgrim (http://diveintomark.org/)
+//  Martin Bialasinki
+// 
+// See scriptaculous.js for full license.  
+
+/* ------------- element ext -------------- */  
+// converts rgb() and #xxx to #xxxxxx format,  
+// returns self (or first argument) if not convertable  
+String.prototype.parseColor = function() {  
+  var color = '#';  
+  if(this.slice(0,4) == 'rgb(') {  
+    var cols = this.slice(4,this.length-1).split(',');  
+    var i=0; do { color += parseInt(cols[i]).toColorPart() } while (++i<3);  
+  } else {  
+    if(this.slice(0,1) == '#') {  
+      if(this.length==4) for(var i=1;i<4;i++) color += (this.charAt(i) + this.charAt(i)).toLowerCase();  
+      if(this.length==7) color = this.toLowerCase();  
+    }  
+  }  
+  return(color.length==7 ? color : (arguments[0] || this));  
+}  
+
+Element.collectTextNodesIgnoreClass = function(element, ignoreclass) {  
+  var children = $(element).childNodes;  
+  var text     = '';  
+  var classtest = new RegExp('^([^ ]+ )*' + ignoreclass+ '( [^ ]+)*$','i');  
+  for (var i = 0; i < children.length; i++) {  
+    if(children[i].nodeType==3) {  
+      text+=children[i].nodeValue;  
+    } else {  
+      if((!children[i].className.match(classtest)) && children[i].hasChildNodes())  
+        text += Element.collectTextNodesIgnoreClass(children[i], ignoreclass);  
+    }  
+  }  
+  return text;
+}
+
+Element.setStyle = function(element, style) {
+  element = $(element);
+  for(k in style) element.style[k.camelize()] = style[k];
+}
+
+Element.setContentZoom = function(element, percent) {  
+  Element.setStyle(element, {fontSize: (percent/100) + 'em'});   
+  if(navigator.appVersion.indexOf('AppleWebKit')>0) window.scrollBy(0,0);  
+}
+
+Element.getOpacity = function(element){  
+  var opacity;
+  if (opacity = Element.getStyle(element, 'opacity'))  
+    return parseFloat(opacity);  
+  if (opacity = (Element.getStyle(element, 'filter') || '').match(/alpha\(opacity=(.*)\)/))  
+    if(opacity[1]) return parseFloat(opacity[1]) / 100;  
+  return 1.0;  
+}
+
+Element.setOpacity = function(element, value){  
+  element= $(element);  
+  if (value == 1){
+    Element.setStyle(element, { opacity: 
+      (/Gecko/.test(navigator.userAgent) && !/Konqueror|Safari|KHTML/.test(navigator.userAgent)) ? 
+      0.999999 : null });
+    if(/MSIE/.test(navigator.userAgent))  
+      Element.setStyle(element, {filter: Element.getStyle(element,'filter').replace(/alpha\([^\)]*\)/gi,'')});  
+  } else {  
+    if(value < 0.00001) value = 0;  
+    Element.setStyle(element, {opacity: value});
+    if(/MSIE/.test(navigator.userAgent))  
+     Element.setStyle(element, 
+       { filter: Element.getStyle(element,'filter').replace(/alpha\([^\)]*\)/gi,'') +
+                 'alpha(opacity='+value*100+')' });  
+  }   
+}  
+Element.getInlineOpacity = function(element){  
+  return $(element).style.opacity || '';
+}  
+
+Element.childrenWithClassName = function(element, className) {  
+  return $A($(element).getElementsByTagName('*')).select(
+    function(c) { return Element.hasClassName(c, className) });
+}
+
+Array.prototype.call = function() {
+  var args = arguments;
+  this.each(function(f){ f.apply(this, args) });
+}
+
+/*--------------------------------------------------------------------------*/
+
+var Effect = {
+  tagifyText: function(element) {
+    var tagifyStyle = 'position:relative';
+    if(/MSIE/.test(navigator.userAgent)) tagifyStyle += ';zoom:1';
+    element = $(element);
+    $A(element.childNodes).each( function(child) {
+      if(child.nodeType==3) {
+        child.nodeValue.toArray().each( function(character) {
+          element.insertBefore(
+            Builder.node('span',{style: tagifyStyle},
+              character == ' ' ? String.fromCharCode(160) : character), 
+              child);
+        });
+        Element.remove(child);
+      }
+    });
+  },
+  multiple: function(element, effect) {
+    var elements;
+    if(((typeof element == 'object') || 
+        (typeof element == 'function')) && 
+       (element.length))
+      elements = element;
+    else
+      elements = $(element).childNodes;
+      
+    var options = Object.extend({
+      speed: 0.1,
+      delay: 0.0
+    }, arguments[2] || {});
+    var masterDelay = options.delay;
+
+    $A(elements).each( function(element, index) {
+      new effect(element, Object.extend(options, { delay: index * options.speed + masterDelay }));
+    });
+  }
+};
+
+var Effect2 = Effect; // deprecated
+
+/* ------------- transitions ------------- */
+
+Effect.Transitions = {}
+
+Effect.Transitions.linear = function(pos) {
+  return pos;
+}
+Effect.Transitions.sinoidal = function(pos) {
+  return (-Math.cos(pos*Math.PI)/2) + 0.5;
+}
+Effect.Transitions.reverse  = function(pos) {
+  return 1-pos;
+}
+Effect.Transitions.flicker = function(pos) {
+  return ((-Math.cos(pos*Math.PI)/4) + 0.75) + Math.random()/4;
+}
+Effect.Transitions.wobble = function(pos) {
+  return (-Math.cos(pos*Math.PI*(9*pos))/2) + 0.5;
+}
+Effect.Transitions.pulse = function(pos) {
+  return (Math.floor(pos*10) % 2 == 0 ? 
+    (pos*10-Math.floor(pos*10)) : 1-(pos*10-Math.floor(pos*10)));
+}
+Effect.Transitions.none = function(pos) {
+  return 0;
+}
+Effect.Transitions.full = function(pos) {
+  return 1;
+}
+
+/* ------------- core effects ------------- */
+
+Effect.Queue = {
+  effects:  [],
+  _each: function(iterator) {
+    this.effects._each(iterator);
+  },
+  interval: null,
+  add: function(effect) {
+    var timestamp = new Date().getTime();
+    
+    switch(effect.options.queue) {
+      case 'front':
+        // move unstarted effects after this effect  
+        this.effects.findAll(function(e){ return e.state=='idle' }).each( function(e) {
+            e.startOn  += effect.finishOn;
+            e.finishOn += effect.finishOn;
+          });
+        break;
+      case 'end':
+        // start effect after last queued effect has finished
+        timestamp = this.effects.pluck('finishOn').max() || timestamp;
+        break;
+    }
+    
+    effect.startOn  += timestamp;
+    effect.finishOn += timestamp;
+    this.effects.push(effect);
+    if(!this.interval) 
+      this.interval = setInterval(this.loop.bind(this), 40);
+  },
+  remove: function(effect) {
+    this.effects = this.effects.reject(function(e) { return e==effect });
+    if(this.effects.length == 0) {
+      clearInterval(this.interval);
+      this.interval = null;
+    }
+  },
+  loop: function() {
+    var timePos = new Date().getTime();
+    this.effects.invoke('loop', timePos);
+  }
+}
+Object.extend(Effect.Queue, Enumerable);
+
+Effect.Base = function() {};
+Effect.Base.prototype = {
+  position: null,
+  setOptions: function(options) {
+    this.options = Object.extend({
+      transition: Effect.Transitions.sinoidal,
+      duration:   1.0,   // seconds
+      fps:        25.0,  // max. 25fps due to Effect.Queue implementation
+      sync:       false, // true for combining
+      from:       0.0,
+      to:         1.0,
+      delay:      0.0,
+      queue:      'parallel'
+    }, options || {});
+  },
+  start: function(options) {
+    this.setOptions(options || {});
+    this.currentFrame = 0;
+    this.state        = 'idle';
+    this.startOn      = this.options.delay*1000;
+    this.finishOn     = this.startOn + (this.options.duration*1000);
+    this.event('beforeStart');
+    if(!this.options.sync) Effect.Queue.add(this);
+  },
+  loop: function(timePos) {
+    if(timePos >= this.startOn) {
+      if(timePos >= this.finishOn) {
+        this.render(1.0);
+        this.cancel();
+        this.event('beforeFinish');
+        if(this.finish) this.finish(); 
+        this.event('afterFinish');
+        return;  
+      }
+      var pos   = (timePos - this.startOn) / (this.finishOn - this.startOn);
+      var frame = Math.round(pos * this.options.fps * this.options.duration);
+      if(frame > this.currentFrame) {
+        this.render(pos);
+        this.currentFrame = frame;
+      }
+    }
+  },
+  render: function(pos) {
+    if(this.state == 'idle') {
+      this.state = 'running';
+      this.event('beforeSetup');
+      if(this.setup) this.setup();
+      this.event('afterSetup');
+    }
+    if(this.state == 'running') {
+      if(this.options.transition) pos = this.options.transition(pos);
+      pos *= (this.options.to-this.options.from);
+      pos += this.options.from;
+      this.position = pos;
+      this.event('beforeUpdate');
+      if(this.update) this.update(pos);
+      this.event('afterUpdate');
+    }
+  },
+  cancel: function() {
+    if(!this.options.sync) Effect.Queue.remove(this);
+    this.state = 'finished';
+  },
+  event: function(eventName) {
+    if(this.options[eventName + 'Internal']) this.options[eventName + 'Internal'](this);
+    if(this.options[eventName]) this.options[eventName](this);
+  },
+  inspect: function() {
+    return '#<Effect:' + $H(this).inspect() + ',options:' + $H(this.options).inspect() + '>';
+  }
+}
+
+Effect.Parallel = Class.create();
+Object.extend(Object.extend(Effect.Parallel.prototype, Effect.Base.prototype), {
+  initialize: function(effects) {
+    this.effects = effects || [];
+    this.start(arguments[1]);
+  },
+  update: function(position) {
+    this.effects.invoke('render', position);
+  },
+  finish: function(position) {
+    this.effects.each( function(effect) {
+      effect.render(1.0);
+      effect.cancel();
+      effect.event('beforeFinish');
+      if(effect.finish) effect.finish(position);
+      effect.event('afterFinish');
+    });
+  }
+});
+
+Effect.Opacity = Class.create();
+Object.extend(Object.extend(Effect.Opacity.prototype, Effect.Base.prototype), {
+  initialize: function(element) {
+    this.element = $(element);
+    // make this work on IE on elements without 'layout'
+    if(/MSIE/.test(navigator.userAgent) && (!this.element.hasLayout))
+      Element.setStyle(this.element, {zoom: 1});
+    var options = Object.extend({
+      from: Element.getOpacity(this.element) || 0.0,
+      to:   1.0
+    }, arguments[1] || {});
+    this.start(options);
+  },
+  update: function(position) {
+    Element.setOpacity(this.element, position);
+  }
+});
+
+Effect.MoveBy = Class.create();
+Object.extend(Object.extend(Effect.MoveBy.prototype, Effect.Base.prototype), {
+  initialize: function(element, toTop, toLeft) {
+    this.element      = $(element);
+    this.toTop        = toTop;
+    this.toLeft       = toLeft;
+    this.start(arguments[3]);
+  },
+  setup: function() {
+    // Bug in Opera: Opera returns the "real" position of a static element or
+    // relative element that does not have top/left explicitly set.
+    // ==> Always set top and left for position relative elements in your stylesheets 
+    // (to 0 if you do not need them) 
+    Element.makePositioned(this.element);
+    this.originalTop  = parseFloat(Element.getStyle(this.element,'top')  || '0');
+    this.originalLeft = parseFloat(Element.getStyle(this.element,'left') || '0');
+  },
+  update: function(position) {
+    Element.setStyle(this.element, {
+      top:  this.toTop  * position + this.originalTop + 'px',
+      left: this.toLeft * position + this.originalLeft + 'px'
+    });
+  }
+});
+
+Effect.Scale = Class.create();
+Object.extend(Object.extend(Effect.Scale.prototype, Effect.Base.prototype), {
+  initialize: function(element, percent) {
+    this.element = $(element)
+    var options = Object.extend({
+      scaleX: true,
+      scaleY: true,
+      scaleContent: true,
+      scaleFromCenter: false,
+      scaleMode: 'box',        // 'box' or 'contents' or {} with provided values
+      scaleFrom: 100.0,
+      scaleTo:   percent
+    }, arguments[2] || {});
+    this.start(options);
+  },
+  setup: function() {
+    this.restoreAfterFinish = this.options.restoreAfterFinish || false;
+    this.elementPositioning = Element.getStyle(this.element,'position');
+    
+    this.originalStyle = {};
+    ['top','left','width','height','fontSize'].each( function(k) {
+      this.originalStyle[k] = this.element.style[k];
+    }.bind(this));
+      
+    this.originalTop  = this.element.offsetTop;
+    this.originalLeft = this.element.offsetLeft;
+    
+    var fontSize = Element.getStyle(this.element,'font-size') || '100%';
+    ['em','px','%'].each( function(fontSizeType) {
+      if(fontSize.indexOf(fontSizeType)>0) {
+        this.fontSize     = parseFloat(fontSize);
+        this.fontSizeType = fontSizeType;
+      }
+    }.bind(this));
+    
+    this.factor = (this.options.scaleTo - this.options.scaleFrom)/100;
+    
+    this.dims = null;
+    if(this.options.scaleMode=='box')
+      this.dims = [this.element.offsetHeight, this.element.offsetWidth];
+    if(/^content/.test(this.options.scaleMode))
+      this.dims = [this.element.scrollHeight, this.element.scrollWidth];
+    if(!this.dims)
+      this.dims = [this.options.scaleMode.originalHeight,
+                   this.options.scaleMode.originalWidth];
+  },
+  update: function(position) {
+    var currentScale = (this.options.scaleFrom/100.0) + (this.factor * position);
+    if(this.options.scaleContent && this.fontSize)
+      Element.setStyle(this.element, {fontSize: this.fontSize * currentScale + this.fontSizeType });
+    this.setDimensions(this.dims[0] * currentScale, this.dims[1] * currentScale);
+  },
+  finish: function(position) {
+    if (this.restoreAfterFinish) Element.setStyle(this.element, this.originalStyle);
+  },
+  setDimensions: function(height, width) {
+    var d = {};
+    if(this.options.scaleX) d.width = width + 'px';
+    if(this.options.scaleY) d.height = height + 'px';
+    if(this.options.scaleFromCenter) {
+      var topd  = (height - this.dims[0])/2;
+      var leftd = (width  - this.dims[1])/2;
+      if(this.elementPositioning == 'absolute') {
+        if(this.options.scaleY) d.top = this.originalTop-topd + 'px';
+        if(this.options.scaleX) d.left = this.originalLeft-leftd + 'px';
+      } else {
+        if(this.options.scaleY) d.top = -topd + 'px';
+        if(this.options.scaleX) d.left = -leftd + 'px';
+      }
+    }
+    Element.setStyle(this.element, d);
+  }
+});
+
+Effect.Highlight = Class.create();
+Object.extend(Object.extend(Effect.Highlight.prototype, Effect.Base.prototype), {
+  initialize: function(element) {
+    this.element = $(element);
+    var options = Object.extend({ startcolor: '#ffff99' }, arguments[1] || {});
+    this.start(options);
+  },
+  setup: function() {
+    // Prevent executing on elements not in the layout flow
+    if(Element.getStyle(this.element, 'display')=='none') { this.cancel(); return; }
+    // Disable background image during the effect
+    this.oldStyle = {
+      backgroundImage: Element.getStyle(this.element, 'background-image') };
+    Element.setStyle(this.element, {backgroundImage: 'none'});
+    if(!this.options.endcolor)
+      this.options.endcolor = Element.getStyle(this.element, 'background-color').parseColor('#ffffff');
+    if(!this.options.restorecolor)
+      this.options.restorecolor = Element.getStyle(this.element, 'background-color');
+    // init color calculations
+    this._base  = $R(0,2).map(function(i){ return parseInt(this.options.startcolor.slice(i*2+1,i*2+3),16) }.bind(this));
+    this._delta = $R(0,2).map(function(i){ return parseInt(this.options.endcolor.slice(i*2+1,i*2+3),16)-this._base[i] }.bind(this));
+  },
+  update: function(position) {
+    Element.setStyle(this.element,{backgroundColor: $R(0,2).inject('#',function(m,v,i){
+      return m+(Math.round(this._base[i]+(this._delta[i]*position)).toColorPart()); }.bind(this)) });
+  },
+  finish: function() {
+    Element.setStyle(this.element, Object.extend(this.oldStyle, {
+      backgroundColor: this.options.restorecolor
+    }));
+  }
+});
+
+Effect.ScrollTo = Class.create();
+Object.extend(Object.extend(Effect.ScrollTo.prototype, Effect.Base.prototype), {
+  initialize: function(element) {
+    this.element = $(element);
+    this.start(arguments[1] || {});
+  },
+  setup: function() {
+    Position.prepare();
+    var offsets = Position.cumulativeOffset(this.element);
+    if(this.options.offset) offsets[1] += this.options.offset;
+    var max = window.innerHeight ? 
+      window.height - window.innerHeight :
+      document.body.scrollHeight - 
+        (document.documentElement.clientHeight ? 
+          document.documentElement.clientHeight : document.body.clientHeight);
+    this.scrollStart = Position.deltaY;
+    this.delta = (offsets[1] > max ? max : offsets[1]) - this.scrollStart;
+  },
+  update: function(position) {
+    Position.prepare();
+    window.scrollTo(Position.deltaX, 
+      this.scrollStart + (position*this.delta));
+  }
+});
+
+/* ------------- combination effects ------------- */
+
+Effect.Fade = function(element) {
+  var oldOpacity = Element.getInlineOpacity(element);
+  var options = Object.extend({
+  from: Element.getOpacity(element) || 1.0,
+  to:   0.0,
+  afterFinishInternal: function(effect) { with(Element) { 
+    if(effect.options.to!=0) return;
+    hide(effect.element);
+    setStyle(effect.element, {opacity: oldOpacity}); }}
+  }, arguments[1] || {});
+  return new Effect.Opacity(element,options);
+}
+
+Effect.Appear = function(element) {
+  var options = Object.extend({
+  from: (Element.getStyle(element, 'display') == 'none' ? 0.0 : Element.getOpacity(element) || 0.0),
+  to:   1.0,
+  beforeSetup: function(effect) { with(Element) {
+    setOpacity(effect.element, effect.options.from);
+    show(effect.element); }}
+  }, arguments[1] || {});
+  return new Effect.Opacity(element,options);
+}
+
+Effect.Puff = function(element) {
+  element = $(element);
+  var oldStyle = { opacity: Element.getInlineOpacity(element), position: Element.getStyle(element, 'position') };
+  return new Effect.Parallel(
+   [ new Effect.Scale(element, 200, 
+      { sync: true, scaleFromCenter: true, scaleContent: true, restoreAfterFinish: true }), 
+     new Effect.Opacity(element, { sync: true, to: 0.0 } ) ], 
+     Object.extend({ duration: 1.0, 
+      beforeSetupInternal: function(effect) { with(Element) {
+        setStyle(effect.effects[0].element, {position: 'absolute'}); }},
+      afterFinishInternal: function(effect) { with(Element) {
+         hide(effect.effects[0].element);
+         setStyle(effect.effects[0].element, oldStyle); }}
+     }, arguments[1] || {})
+   );
+}
+
+Effect.BlindUp = function(element) {
+  element = $(element);
+  Element.makeClipping(element);
+  return new Effect.Scale(element, 0, 
+    Object.extend({ scaleContent: false, 
+      scaleX: false, 
+      restoreAfterFinish: true,
+      afterFinishInternal: function(effect) { with(Element) {
+        [hide, undoClipping].call(effect.element); }} 
+    }, arguments[1] || {})
+  );
+}
+
+Effect.BlindDown = function(element) {
+  element = $(element);
+  var oldHeight = Element.getStyle(element, 'height');
+  var elementDimensions = Element.getDimensions(element);
+  return new Effect.Scale(element, 100, 
+    Object.extend({ scaleContent: false, 
+      scaleX: false,
+      scaleFrom: 0,
+      scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width},
+      restoreAfterFinish: true,
+      afterSetup: function(effect) { with(Element) {
+        makeClipping(effect.element);
+        setStyle(effect.element, {height: '0px'});
+        show(effect.element); 
+      }},  
+      afterFinishInternal: function(effect) { with(Element) {
+        undoClipping(effect.element);
+        setStyle(effect.element, {height: oldHeight});
+      }}
+    }, arguments[1] || {})
+  );
+}
+
+Effect.SwitchOff = function(element) {
+  element = $(element);
+  var oldOpacity = Element.getInlineOpacity(element);
+  return new Effect.Appear(element, { 
+    duration: 0.4,
+    from: 0,
+    transition: Effect.Transitions.flicker,
+    afterFinishInternal: function(effect) {
+      new Effect.Scale(effect.element, 1, { 
+        duration: 0.3, scaleFromCenter: true,
+        scaleX: false, scaleContent: false, restoreAfterFinish: true,
+        beforeSetup: function(effect) { with(Element) {
+          [makePositioned,makeClipping].call(effect.element);
+        }},
+        afterFinishInternal: function(effect) { with(Element) {
+          [hide,undoClipping,undoPositioned].call(effect.element);
+          setStyle(effect.element, {opacity: oldOpacity});
+        }}
+      })
+    }
+  });
+}
+
+Effect.DropOut = function(element) {
+  element = $(element);
+  var oldStyle = {
+    top: Element.getStyle(element, 'top'),
+    left: Element.getStyle(element, 'left'),
+    opacity: Element.getInlineOpacity(element) };
+  return new Effect.Parallel(
+    [ new Effect.MoveBy(element, 100, 0, { sync: true }), 
+      new Effect.Opacity(element, { sync: true, to: 0.0 }) ],
+    Object.extend(
+      { duration: 0.5,
+        beforeSetup: function(effect) { with(Element) {
+          makePositioned(effect.effects[0].element); }},
+        afterFinishInternal: function(effect) { with(Element) {
+          [hide, undoPositioned].call(effect.effects[0].element);
+          setStyle(effect.effects[0].element, oldStyle); }} 
+      }, arguments[1] || {}));
+}
+
+Effect.Shake = function(element) {
+  element = $(element);
+  var oldStyle = {
+    top: Element.getStyle(element, 'top'),
+    left: Element.getStyle(element, 'left') };
+  return new Effect.MoveBy(element, 0, 20, 
+    { duration: 0.05, afterFinishInternal: function(effect) {
+  new Effect.MoveBy(effect.element, 0, -40, 
+    { duration: 0.1, afterFinishInternal: function(effect) {
+  new Effect.MoveBy(effect.element, 0, 40, 
+    { duration: 0.1, afterFinishInternal: function(effect) {
+  new Effect.MoveBy(effect.element, 0, -40, 
+    { duration: 0.1, afterFinishInternal: function(effect) {
+  new Effect.MoveBy(effect.element, 0, 40, 
+    { duration: 0.1, afterFinishInternal: function(effect) {
+  new Effect.MoveBy(effect.element, 0, -20, 
+    { duration: 0.05, afterFinishInternal: function(effect) { with(Element) {
+        undoPositioned(effect.element);
+        setStyle(effect.element, oldStyle);
+  }}}) }}) }}) }}) }}) }});
+}
+
+Effect.SlideDown = function(element) {
+  element = $(element);
+  Element.cleanWhitespace(element);
+  // SlideDown need to have the content of the element wrapped in a container element with fixed height!
+  var oldInnerBottom = Element.getStyle(element.firstChild, 'bottom');
+  var elementDimensions = Element.getDimensions(element);
+  return new Effect.Scale(element, 100, Object.extend({ 
+    scaleContent: false, 
+    scaleX: false, 
+    scaleFrom: 0,
+    scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width},
+    restoreAfterFinish: true,
+    afterSetup: function(effect) { with(Element) {
+      makePositioned(effect.element);
+      makePositioned(effect.element.firstChild);
+      if(window.opera) setStyle(effect.element, {top: ''});
+      makeClipping(effect.element);
+      setStyle(effect.element, {height: '0px'});
+      show(element); }},
+    afterUpdateInternal: function(effect) { with(Element) {
+      setStyle(effect.element.firstChild, {bottom:
+        (effect.dims[0] - effect.element.clientHeight) + 'px' }); }},
+    afterFinishInternal: function(effect) { with(Element) {
+      undoClipping(effect.element); 
+      undoPositioned(effect.element.firstChild);
+      undoPositioned(effect.element);
+      setStyle(effect.element.firstChild, {bottom: oldInnerBottom}); }}
+    }, arguments[1] || {})
+  );
+}
+  
+Effect.SlideUp = function(element) {
+  element = $(element);
+  Element.cleanWhitespace(element);
+  var oldInnerBottom = Element.getStyle(element.firstChild, 'bottom');
+  return new Effect.Scale(element, 0, 
+   Object.extend({ scaleContent: false, 
+    scaleX: false, 
+    scaleMode: 'box',
+    scaleFrom: 100,
+    restoreAfterFinish: true,
+    beforeStartInternal: function(effect) { with(Element) {
+      makePositioned(effect.element);
+      makePositioned(effect.element.firstChild);
+      if(window.opera) setStyle(effect.element, {top: ''});
+      makeClipping(effect.element);
+      show(element); }},  
+    afterUpdateInternal: function(effect) { with(Element) {
+      setStyle(effect.element.firstChild, {bottom:
+        (effect.dims[0] - effect.element.clientHeight) + 'px' }); }},
+    afterFinishInternal: function(effect) { with(Element) {
+        [hide, undoClipping].call(effect.element); 
+        undoPositioned(effect.element.firstChild);
+        undoPositioned(effect.element);
+        setStyle(effect.element.firstChild, {bottom: oldInnerBottom}); }}
+   }, arguments[1] || {})
+  );
+}
+
+// Bug in opera makes the TD containing this element expand for a instance after finish 
+Effect.Squish = function(element) {
+  return new Effect.Scale(element, window.opera ? 1 : 0, 
+    { restoreAfterFinish: true,
+      beforeSetup: function(effect) { with(Element) {
+        makeClipping(effect.element); }},  
+      afterFinishInternal: function(effect) { with(Element) {
+        hide(effect.element); 
+        undoClipping(effect.element); }}
+  });
+}
+
+Effect.Grow = function(element) {
+  element = $(element);
+  var options = Object.extend({
+    direction: 'center',
+    moveTransistion: Effect.Transitions.sinoidal,
+    scaleTransition: Effect.Transitions.sinoidal,
+    opacityTransition: Effect.Transitions.full
+  }, arguments[1] || {});
+  var oldStyle = {
+    top: element.style.top,
+    left: element.style.left,
+    height: element.style.height,
+    width: element.style.width,
+    opacity: Element.getInlineOpacity(element) };
+
+  var dims = Element.getDimensions(element);    
+  var initialMoveX, initialMoveY;
+  var moveX, moveY;
+  
+  switch (options.direction) {
+    case 'top-left':
+      initialMoveX = initialMoveY = moveX = moveY = 0; 
+      break;
+    case 'top-right':
+      initialMoveX = dims.width;
+      initialMoveY = moveY = 0;
+      moveX = -dims.width;
+      break;
+    case 'bottom-left':
+      initialMoveX = moveX = 0;
+      initialMoveY = dims.height;
+      moveY = -dims.height;
+      break;
+    case 'bottom-right':
+      initialMoveX = dims.width;
+      initialMoveY = dims.height;
+      moveX = -dims.width;
+      moveY = -dims.height;
+      break;
+    case 'center':
+      initialMoveX = dims.width / 2;
+      initialMoveY = dims.height / 2;
+      moveX = -dims.width / 2;
+      moveY = -dims.height / 2;
+      break;
+  }
+  
+  return new Effect.MoveBy(element, initialMoveY, initialMoveX, { 
+    duration: 0.01, 
+    beforeSetup: function(effect) { with(Element) {
+      hide(effect.element);
+      makeClipping(effect.element);
+      makePositioned(effect.element);
+    }},
+    afterFinishInternal: function(effect) {
+      new Effect.Parallel(
+        [ new Effect.Opacity(effect.element, { sync: true, to: 1.0, from: 0.0, transition: options.opacityTransition }),
+          new Effect.MoveBy(effect.element, moveY, moveX, { sync: true, transition: options.moveTransition }),
+          new Effect.Scale(effect.element, 100, {
+            scaleMode: { originalHeight: dims.height, originalWidth: dims.width }, 
+            sync: true, scaleFrom: window.opera ? 1 : 0, transition: options.scaleTransition, restoreAfterFinish: true})
+        ], Object.extend({
+             beforeSetup: function(effect) { with(Element) {
+               setStyle(effect.effects[0].element, {height: '0px'});
+               show(effect.effects[0].element); }},
+             afterFinishInternal: function(effect) { with(Element) {
+               [undoClipping, undoPositioned].call(effect.effects[0].element); 
+               setStyle(effect.effects[0].element, oldStyle); }}
+           }, options)
+      )
+    }
+  });
+}
+
+Effect.Shrink = function(element) {
+  element = $(element);
+  var options = Object.extend({
+    direction: 'center',
+    moveTransistion: Effect.Transitions.sinoidal,
+    scaleTransition: Effect.Transitions.sinoidal,
+    opacityTransition: Effect.Transitions.none
+  }, arguments[1] || {});
+  var oldStyle = {
+    top: element.style.top,
+    left: element.style.left,
+    height: element.style.height,
+    width: element.style.width,
+    opacity: Element.getInlineOpacity(element) };
+
+  var dims = Element.getDimensions(element);
+  var moveX, moveY;
+  
+  switch (options.direction) {
+    case 'top-left':
+      moveX = moveY = 0;
+      break;
+    case 'top-right':
+      moveX = dims.width;
+      moveY = 0;
+      break;
+    case 'bottom-left':
+      moveX = 0;
+      moveY = dims.height;
+      break;
+    case 'bottom-right':
+      moveX = dims.width;
+      moveY = dims.height;
+      break;
+    case 'center':  
+      moveX = dims.width / 2;
+      moveY = dims.height / 2;
+      break;
+  }
+  
+  return new Effect.Parallel(
+    [ new Effect.Opacity(element, { sync: true, to: 0.0, from: 1.0, transition: options.opacityTransition }),
+      new Effect.Scale(element, window.opera ? 1 : 0, { sync: true, transition: options.scaleTransition, restoreAfterFinish: true}),
+      new Effect.MoveBy(element, moveY, moveX, { sync: true, transition: options.moveTransition })
+    ], Object.extend({            
+         beforeStartInternal: function(effect) { with(Element) {
+           [makePositioned, makeClipping].call(effect.effects[0].element) }},
+         afterFinishInternal: function(effect) { with(Element) {
+           [hide, undoClipping, undoPositioned].call(effect.effects[0].element);
+           setStyle(effect.effects[0].element, oldStyle); }}
+       }, options)
+  );
+}
+
+Effect.Pulsate = function(element) {
+  element = $(element);
+  var options    = arguments[1] || {};
+  var oldOpacity = Element.getInlineOpacity(element);
+  var transition = options.transition || Effect.Transitions.sinoidal;
+  var reverser   = function(pos){ return transition(1-Effect.Transitions.pulse(pos)) };
+  reverser.bind(transition);
+  return new Effect.Opacity(element, 
+    Object.extend(Object.extend({  duration: 3.0, from: 0,
+      afterFinishInternal: function(effect) { Element.setStyle(effect.element, {opacity: oldOpacity}); }
+    }, options), {transition: reverser}));
+}
+
+Effect.Fold = function(element) {
+  element = $(element);
+  var oldStyle = {
+    top: element.style.top,
+    left: element.style.left,
+    width: element.style.width,
+    height: element.style.height };
+  Element.makeClipping(element);
+  return new Effect.Scale(element, 5, Object.extend({   
+    scaleContent: false,
+    scaleX: false,
+    afterFinishInternal: function(effect) {
+    new Effect.Scale(element, 1, { 
+      scaleContent: false, 
+      scaleY: false,
+      afterFinishInternal: function(effect) { with(Element) {
+        [hide, undoClipping].call(effect.element); 
+        setStyle(effect.element, oldStyle);
+      }} });
+  }}, arguments[1] || {}));
+}
diff --git a/vendor/gems/ruby-openid-2.1.4/examples/rails_openid/public/javascripts/prototype.js b/vendor/gems/ruby-openid-2.1.4/examples/rails_openid/public/javascripts/prototype.js
new file mode 100644 (file)
index 0000000..e9ccd3c
--- /dev/null
@@ -0,0 +1,1785 @@
+/*  Prototype JavaScript framework, version 1.4.0
+ *  (c) 2005 Sam Stephenson <sam@conio.net>
+ *
+ *  THIS FILE IS AUTOMATICALLY GENERATED. When sending patches, please diff
+ *  against the source tree, available from the Prototype darcs repository.
+ *
+ *  Prototype is freely distributable under the terms of an MIT-style license.
+ *
+ *  For details, see the Prototype web site: http://prototype.conio.net/
+ *
+/*--------------------------------------------------------------------------*/
+
+var Prototype = {
+  Version: '1.4.0',
+  ScriptFragment: '(?:<script.*?>)((\n|\r|.)*?)(?:<\/script>)',
+
+  emptyFunction: function() {},
+  K: function(x) {return x}
+}
+
+var Class = {
+  create: function() {
+    return function() {
+      this.initialize.apply(this, arguments);
+    }
+  }
+}
+
+var Abstract = new Object();
+
+Object.extend = function(destination, source) {
+  for (property in source) {
+    destination[property] = source[property];
+  }
+  return destination;
+}
+
+Object.inspect = function(object) {
+  try {
+    if (object == undefined) return 'undefined';
+    if (object == null) return 'null';
+    return object.inspect ? object.inspect() : object.toString();
+  } catch (e) {
+    if (e instanceof RangeError) return '...';
+    throw e;
+  }
+}
+
+Function.prototype.bind = function() {
+  var __method = this, args = $A(arguments), object = args.shift();
+  return function() {
+    return __method.apply(object, args.concat($A(arguments)));
+  }
+}
+
+Function.prototype.bindAsEventListener = function(object) {
+  var __method = this;
+  return function(event) {
+    return __method.call(object, event || window.event);
+  }
+}
+
+Object.extend(Number.prototype, {
+  toColorPart: function() {
+    var digits = this.toString(16);
+    if (this < 16) return '0' + digits;
+    return digits;
+  },
+
+  succ: function() {
+    return this + 1;
+  },
+
+  times: function(iterator) {
+    $R(0, this, true).each(iterator);
+    return this;
+  }
+});
+
+var Try = {
+  these: function() {
+    var returnValue;
+
+    for (var i = 0; i < arguments.length; i++) {
+      var lambda = arguments[i];
+      try {
+        returnValue = lambda();
+        break;
+      } catch (e) {}
+    }
+
+    return returnValue;
+  }
+}
+
+/*--------------------------------------------------------------------------*/
+
+var PeriodicalExecuter = Class.create();
+PeriodicalExecuter.prototype = {
+  initialize: function(callback, frequency) {
+    this.callback = callback;
+    this.frequency = frequency;
+    this.currentlyExecuting = false;
+
+    this.registerCallback();
+  },
+
+  registerCallback: function() {
+    setInterval(this.onTimerEvent.bind(this), this.frequency * 1000);
+  },
+
+  onTimerEvent: function() {
+    if (!this.currentlyExecuting) {
+      try {
+        this.currentlyExecuting = true;
+        this.callback();
+      } finally {
+        this.currentlyExecuting = false;
+      }
+    }
+  }
+}
+
+/*--------------------------------------------------------------------------*/
+
+function $() {
+  var elements = new Array();
+
+  for (var i = 0; i < arguments.length; i++) {
+    var element = arguments[i];
+    if (typeof element == 'string')
+      element = document.getElementById(element);
+
+    if (arguments.length == 1)
+      return element;
+
+    elements.push(element);
+  }
+
+  return elements;
+}
+Object.extend(String.prototype, {
+  stripTags: function() {
+    return this.replace(/<\/?[^>]+>/gi, '');
+  },
+
+  stripScripts: function() {
+    return this.replace(new RegExp(Prototype.ScriptFragment, 'img'), '');
+  },
+
+  extractScripts: function() {
+    var matchAll = new RegExp(Prototype.ScriptFragment, 'img');
+    var matchOne = new RegExp(Prototype.ScriptFragment, 'im');
+    return (this.match(matchAll) || []).map(function(scriptTag) {
+      return (scriptTag.match(matchOne) || ['', ''])[1];
+    });
+  },
+
+  evalScripts: function() {
+    return this.extractScripts().map(eval);
+  },
+
+  escapeHTML: function() {
+    var div = document.createElement('div');
+    var text = document.createTextNode(this);
+    div.appendChild(text);
+    return div.innerHTML;
+  },
+
+  unescapeHTML: function() {
+    var div = document.createElement('div');
+    div.innerHTML = this.stripTags();
+    return div.childNodes[0] ? div.childNodes[0].nodeValue : '';
+  },
+
+  toQueryParams: function() {
+    var pairs = this.match(/^\??(.*)$/)[1].split('&');
+    return pairs.inject({}, function(params, pairString) {
+      var pair = pairString.split('=');
+      params[pair[0]] = pair[1];
+      return params;
+    });
+  },
+
+  toArray: function() {
+    return this.split('');
+  },
+
+  camelize: function() {
+    var oStringList = this.split('-');
+    if (oStringList.length == 1) return oStringList[0];
+
+    var camelizedString = this.indexOf('-') == 0
+      ? oStringList[0].charAt(0).toUpperCase() + oStringList[0].substring(1)
+      : oStringList[0];
+
+    for (var i = 1, len = oStringList.length; i < len; i++) {
+      var s = oStringList[i];
+      camelizedString += s.charAt(0).toUpperCase() + s.substring(1);
+    }
+
+    return camelizedString;
+  },
+
+  inspect: function() {
+    return "'" + this.replace('\\', '\\\\').replace("'", '\\\'') + "'";
+  }
+});
+
+String.prototype.parseQuery = String.prototype.toQueryParams;
+
+var $break    = new Object();
+var $continue = new Object();
+
+var Enumerable = {
+  each: function(iterator) {
+    var index = 0;
+    try {
+      this._each(function(value) {
+        try {
+          iterator(value, index++);
+        } catch (e) {
+          if (e != $continue) throw e;
+        }
+      });
+    } catch (e) {
+      if (e != $break) throw e;
+    }
+  },
+
+  all: function(iterator) {
+    var result = true;
+    this.each(function(value, index) {
+      result = result && !!(iterator || Prototype.K)(value, index);
+      if (!result) throw $break;
+    });
+    return result;
+  },
+
+  any: function(iterator) {
+    var result = true;
+    this.each(function(value, index) {
+      if (result = !!(iterator || Prototype.K)(value, index))
+        throw $break;
+    });
+    return result;
+  },
+
+  collect: function(iterator) {
+    var results = [];
+    this.each(function(value, index) {
+      results.push(iterator(value, index));
+    });
+    return results;
+  },
+
+  detect: function (iterator) {
+    var result;
+    this.each(function(value, index) {
+      if (iterator(value, index)) {
+        result = value;
+        throw $break;
+      }
+    });
+    return result;
+  },
+
+  findAll: function(iterator) {
+    var results = [];
+    this.each(function(value, index) {
+      if (iterator(value, index))
+        results.push(value);
+    });
+    return results;
+  },
+
+  grep: function(pattern, iterator) {
+    var results = [];
+    this.each(function(value, index) {
+      var stringValue = value.toString();
+      if (stringValue.match(pattern))
+        results.push((iterator || Prototype.K)(value, index));
+    })
+    return results;
+  },
+
+  include: function(object) {
+    var found = false;
+    this.each(function(value) {
+      if (value == object) {
+        found = true;
+        throw $break;
+      }
+    });
+    return found;
+  },
+
+  inject: function(memo, iterator) {
+    this.each(function(value, index) {
+      memo = iterator(memo, value, index);
+    });
+    return memo;
+  },
+
+  invoke: function(method) {
+    var args = $A(arguments).slice(1);
+    return this.collect(function(value) {
+      return value[method].apply(value, args);
+    });
+  },
+
+  max: function(iterator) {
+    var result;
+    this.each(function(value, index) {
+      value = (iterator || Prototype.K)(value, index);
+      if (value >= (result || value))
+        result = value;
+    });
+    return result;
+  },
+
+  min: function(iterator) {
+    var result;
+    this.each(function(value, index) {
+      value = (iterator || Prototype.K)(value, index);
+      if (value <= (result || value))
+        result = value;
+    });
+    return result;
+  },
+
+  partition: function(iterator) {
+    var trues = [], falses = [];
+    this.each(function(value, index) {
+      ((iterator || Prototype.K)(value, index) ?
+        trues : falses).push(value);
+    });
+    return [trues, falses];
+  },
+
+  pluck: function(property) {
+    var results = [];
+    this.each(function(value, index) {
+      results.push(value[property]);
+    });
+    return results;
+  },
+
+  reject: function(iterator) {
+    var results = [];
+    this.each(function(value, index) {
+      if (!iterator(value, index))
+        results.push(value);
+    });
+    return results;
+  },
+
+  sortBy: function(iterator) {
+    return this.collect(function(value, index) {
+      return {value: value, criteria: iterator(value, index)};
+    }).sort(function(left, right) {
+      var a = left.criteria, b = right.criteria;
+      return a < b ? -1 : a > b ? 1 : 0;
+    }).pluck('value');
+  },
+
+  toArray: function() {
+    return this.collect(Prototype.K);
+  },
+
+  zip: function() {
+    var iterator = Prototype.K, args = $A(arguments);
+    if (typeof args.last() == 'function')
+      iterator = args.pop();
+
+    var collections = [this].concat(args).map($A);
+    return this.map(function(value, index) {
+      iterator(value = collections.pluck(index));
+      return value;
+    });
+  },
+
+  inspect: function() {
+    return '#<Enumerable:' + this.toArray().inspect() + '>';
+  }
+}
+
+Object.extend(Enumerable, {
+  map:     Enumerable.collect,
+  find:    Enumerable.detect,
+  select:  Enumerable.findAll,
+  member:  Enumerable.include,
+  entries: Enumerable.toArray
+});
+var $A = Array.from = function(iterable) {
+  if (!iterable) return [];
+  if (iterable.toArray) {
+    return iterable.toArray();
+  } else {
+    var results = [];
+    for (var i = 0; i < iterable.length; i++)
+      results.push(iterable[i]);
+    return results;
+  }
+}
+
+Object.extend(Array.prototype, Enumerable);
+
+Array.prototype._reverse = Array.prototype.reverse;
+
+Object.extend(Array.prototype, {
+  _each: function(iterator) {
+    for (var i = 0; i < this.length; i++)
+      iterator(this[i]);
+  },
+
+  clear: function() {
+    this.length = 0;
+    return this;
+  },
+
+  first: function() {
+    return this[0];
+  },
+
+  last: function() {
+    return this[this.length - 1];
+  },
+
+  compact: function() {
+    return this.select(function(value) {
+      return value != undefined || value != null;
+    });
+  },
+
+  flatten: function() {
+    return this.inject([], function(array, value) {
+      return array.concat(value.constructor == Array ?
+        value.flatten() : [value]);
+    });
+  },
+
+  without: function() {
+    var values = $A(arguments);
+    return this.select(function(value) {
+      return !values.include(value);
+    });
+  },
+
+  indexOf: function(object) {
+    for (var i = 0; i < this.length; i++)
+      if (this[i] == object) return i;
+    return -1;
+  },
+
+  reverse: function(inline) {
+    return (inline !== false ? this : this.toArray())._reverse();
+  },
+
+  shift: function() {
+    var result = this[0];
+    for (var i = 0; i < this.length - 1; i++)
+      this[i] = this[i + 1];
+    this.length--;
+    return result;
+  },
+
+  inspect: function() {
+    return '[' + this.map(Object.inspect).join(', ') + ']';
+  }
+});
+var Hash = {
+  _each: function(iterator) {
+    for (key in this) {
+      var value = this[key];
+      if (typeof value == 'function') continue;
+
+      var pair = [key, value];
+      pair.key = key;
+      pair.value = value;
+      iterator(pair);
+    }
+  },
+
+  keys: function() {
+    return this.pluck('key');
+  },
+
+  values: function() {
+    return this.pluck('value');
+  },
+
+  merge: function(hash) {
+    return $H(hash).inject($H(this), function(mergedHash, pair) {
+      mergedHash[pair.key] = pair.value;
+      return mergedHash;
+    });
+  },
+
+  toQueryString: function() {
+    return this.map(function(pair) {
+      return pair.map(encodeURIComponent).join('=');
+    }).join('&');
+  },
+
+  inspect: function() {
+    return '#<Hash:{' + this.map(function(pair) {
+      return pair.map(Object.inspect).join(': ');
+    }).join(', ') + '}>';
+  }
+}
+
+function $H(object) {
+  var hash = Object.extend({}, object || {});
+  Object.extend(hash, Enumerable);
+  Object.extend(hash, Hash);
+  return hash;
+}
+ObjectRange = Class.create();
+Object.extend(ObjectRange.prototype, Enumerable);
+Object.extend(ObjectRange.prototype, {
+  initialize: function(start, end, exclusive) {
+    this.start = start;
+    this.end = end;
+    this.exclusive = exclusive;
+  },
+
+  _each: function(iterator) {
+    var value = this.start;
+    do {
+      iterator(value);
+      value = value.succ();
+    } while (this.include(value));
+  },
+
+  include: function(value) {
+    if (value < this.start)
+      return false;
+    if (this.exclusive)
+      return value < this.end;
+    return value <= this.end;
+  }
+});
+
+var $R = function(start, end, exclusive) {
+  return new ObjectRange(start, end, exclusive);
+}
+
+var Ajax = {
+  getTransport: function() {
+    return Try.these(
+      function() {return new ActiveXObject('Msxml2.XMLHTTP')},
+      function() {return new ActiveXObject('Microsoft.XMLHTTP')},
+      function() {return new XMLHttpRequest()}
+    ) || false;
+  },
+
+  activeRequestCount: 0
+}
+
+Ajax.Responders = {
+  responders: [],
+
+  _each: function(iterator) {
+    this.responders._each(iterator);
+  },
+
+  register: function(responderToAdd) {
+    if (!this.include(responderToAdd))
+      this.responders.push(responderToAdd);
+  },
+
+  unregister: function(responderToRemove) {
+    this.responders = this.responders.without(responderToRemove);
+  },
+
+  dispatch: function(callback, request, transport, json) {
+    this.each(function(responder) {
+      if (responder[callback] && typeof responder[callback] == 'function') {
+        try {
+          responder[callback].apply(responder, [request, transport, json]);
+        } catch (e) {}
+      }
+    });
+  }
+};
+
+Object.extend(Ajax.Responders, Enumerable);
+
+Ajax.Responders.register({
+  onCreate: function() {
+    Ajax.activeRequestCount++;
+  },
+
+  onComplete: function() {
+    Ajax.activeRequestCount--;
+  }
+});
+
+Ajax.Base = function() {};
+Ajax.Base.prototype = {
+  setOptions: function(options) {
+    this.options = {
+      method:       'post',
+      asynchronous: true,
+      parameters:   ''
+    }
+    Object.extend(this.options, options || {});
+  },
+
+  responseIsSuccess: function() {
+    return this.transport.status == undefined
+        || this.transport.status == 0
+        || (this.transport.status >= 200 && this.transport.status < 300);
+  },
+
+  responseIsFailure: function() {
+    return !this.responseIsSuccess();
+  }
+}
+
+Ajax.Request = Class.create();
+Ajax.Request.Events =
+  ['Uninitialized', 'Loading', 'Loaded', 'Interactive', 'Complete'];
+
+Ajax.Request.prototype = Object.extend(new Ajax.Base(), {
+  initialize: function(url, options) {
+    this.transport = Ajax.getTransport();
+    this.setOptions(options);
+    this.request(url);
+  },
+
+  request: function(url) {
+    var parameters = this.options.parameters || '';
+    if (parameters.length > 0) parameters += '&_=';
+
+    try {
+      this.url = url;
+      if (this.options.method == 'get' && parameters.length > 0)
+        this.url += (this.url.match(/\?/) ? '&' : '?') + parameters;
+
+      Ajax.Responders.dispatch('onCreate', this, this.transport);
+
+      this.transport.open(this.options.method, this.url,
+        this.options.asynchronous);
+
+      if (this.options.asynchronous) {
+        this.transport.onreadystatechange = this.onStateChange.bind(this);
+        setTimeout((function() {this.respondToReadyState(1)}).bind(this), 10);
+      }
+
+      this.setRequestHeaders();
+
+      var body = this.options.postBody ? this.options.postBody : parameters;
+      this.transport.send(this.options.method == 'post' ? body : null);
+
+    } catch (e) {
+      this.dispatchException(e);
+    }
+  },
+
+  setRequestHeaders: function() {
+    var requestHeaders =
+      ['X-Requested-With', 'XMLHttpRequest',
+       'X-Prototype-Version', Prototype.Version];
+
+    if (this.options.method == 'post') {
+      requestHeaders.push('Content-type',
+        'application/x-www-form-urlencoded');
+
+      /* Force "Connection: close" for Mozilla browsers to work around
+       * a bug where XMLHttpReqeuest sends an incorrect Content-length
+       * header. See Mozilla Bugzilla #246651.
+       */
+      if (this.transport.overrideMimeType)
+        requestHeaders.push('Connection', 'close');
+    }
+
+    if (this.options.requestHeaders)
+      requestHeaders.push.apply(requestHeaders, this.options.requestHeaders);
+
+    for (var i = 0; i < requestHeaders.length; i += 2)
+      this.transport.setRequestHeader(requestHeaders[i], requestHeaders[i+1]);
+  },
+
+  onStateChange: function() {
+    var readyState = this.transport.readyState;
+    if (readyState != 1)
+      this.respondToReadyState(this.transport.readyState);
+  },
+
+  header: function(name) {
+    try {
+      return this.transport.getResponseHeader(name);
+    } catch (e) {}
+  },
+
+  evalJSON: function() {
+    try {
+      return eval(this.header('X-JSON'));
+    } catch (e) {}
+  },
+
+  evalResponse: function() {
+    try {
+      return eval(this.transport.responseText);
+    } catch (e) {
+      this.dispatchException(e);
+    }
+  },
+
+  respondToReadyState: function(readyState) {
+    var event = Ajax.Request.Events[readyState];
+    var transport = this.transport, json = this.evalJSON();
+
+    if (event == 'Complete') {
+      try {
+        (this.options['on' + this.transport.status]
+         || this.options['on' + (this.responseIsSuccess() ? 'Success' : 'Failure')]
+         || Prototype.emptyFunction)(transport, json);
+      } catch (e) {
+        this.dispatchException(e);
+      }
+
+      if ((this.header('Content-type') || '').match(/^text\/javascript/i))
+        this.evalResponse();
+    }
+
+    try {
+      (this.options['on' + event] || Prototype.emptyFunction)(transport, json);
+      Ajax.Responders.dispatch('on' + event, this, transport, json);
+    } catch (e) {
+      this.dispatchException(e);
+    }
+
+    /* Avoid memory leak in MSIE: clean up the oncomplete event handler */
+    if (event == 'Complete')
+      this.transport.onreadystatechange = Prototype.emptyFunction;
+  },
+
+  dispatchException: function(exception) {
+    (this.options.onException || Prototype.emptyFunction)(this, exception);
+    Ajax.Responders.dispatch('onException', this, exception);
+  }
+});
+
+Ajax.Updater = Class.create();
+
+Object.extend(Object.extend(Ajax.Updater.prototype, Ajax.Request.prototype), {
+  initialize: function(container, url, options) {
+    this.containers = {
+      success: container.success ? $(container.success) : $(container),
+      failure: container.failure ? $(container.failure) :
+        (container.success ? null : $(container))
+    }
+
+    this.transport = Ajax.getTransport();
+    this.setOptions(options);
+
+    var onComplete = this.options.onComplete || Prototype.emptyFunction;
+    this.options.onComplete = (function(transport, object) {
+      this.updateContent();
+      onComplete(transport, object);
+    }).bind(this);
+
+    this.request(url);
+  },
+
+  updateContent: function() {
+    var receiver = this.responseIsSuccess() ?
+      this.containers.success : this.containers.failure;
+    var response = this.transport.responseText;
+
+    if (!this.options.evalScripts)
+      response = response.stripScripts();
+
+    if (receiver) {
+      if (this.options.insertion) {
+        new this.options.insertion(receiver, response);
+      } else {
+        Element.update(receiver, response);
+      }
+    }
+
+    if (this.responseIsSuccess()) {
+      if (this.onComplete)
+        setTimeout(this.onComplete.bind(this), 10);
+    }
+  }
+});
+
+Ajax.PeriodicalUpdater = Class.create();
+Ajax.PeriodicalUpdater.prototype = Object.extend(new Ajax.Base(), {
+  initialize: function(container, url, options) {
+    this.setOptions(options);
+    this.onComplete = this.options.onComplete;
+
+    this.frequency = (this.options.frequency || 2);
+    this.decay = (this.options.decay || 1);
+
+    this.updater = {};
+    this.container = container;
+    this.url = url;
+
+    this.start();
+  },
+
+  start: function() {
+    this.options.onComplete = this.updateComplete.bind(this);
+    this.onTimerEvent();
+  },
+
+  stop: function() {
+    this.updater.onComplete = undefined;
+    clearTimeout(this.timer);
+    (this.onComplete || Prototype.emptyFunction).apply(this, arguments);
+  },
+
+  updateComplete: function(request) {
+    if (this.options.decay) {
+      this.decay = (request.responseText == this.lastText ?
+        this.decay * this.options.decay : 1);
+
+      this.lastText = request.responseText;
+    }
+    this.timer = setTimeout(this.onTimerEvent.bind(this),
+      this.decay * this.frequency * 1000);
+  },
+
+  onTimerEvent: function() {
+    this.updater = new Ajax.Updater(this.container, this.url, this.options);
+  }
+});
+document.getElementsByClassName = function(className, parentElement) {
+  var children = ($(parentElement) || document.body).getElementsByTagName('*');
+  return $A(children).inject([], function(elements, child) {
+    if (child.className.match(new RegExp("(^|\\s)" + className + "(\\s|$)")))
+      elements.push(child);
+    return elements;
+  });
+}
+
+/*--------------------------------------------------------------------------*/
+
+if (!window.Element) {
+  var Element = new Object();
+}
+
+Object.extend(Element, {
+  visible: function(element) {
+    return $(element).style.display != 'none';
+  },
+
+  toggle: function() {
+    for (var i = 0; i < arguments.length; i++) {
+      var element = $(arguments[i]);
+      Element[Element.visible(element) ? 'hide' : 'show'](element);
+    }
+  },
+
+  hide: function() {
+    for (var i = 0; i < arguments.length; i++) {
+      var element = $(arguments[i]);
+      element.style.display = 'none';
+    }
+  },
+
+  show: function() {
+    for (var i = 0; i < arguments.length; i++) {
+      var element = $(arguments[i]);
+      element.style.display = '';
+    }
+  },
+
+  remove: function(element) {
+    element = $(element);
+    element.parentNode.removeChild(element);
+  },
+
+  update: function(element, html) {
+    $(element).innerHTML = html.stripScripts();
+    setTimeout(function() {html.evalScripts()}, 10);
+  },
+
+  getHeight: function(element) {
+    element = $(element);
+    return element.offsetHeight;
+  },
+
+  classNames: function(element) {
+    return new Element.ClassNames(element);
+  },
+
+  hasClassName: function(element, className) {
+    if (!(element = $(element))) return;
+    return Element.classNames(element).include(className);
+  },
+
+  addClassName: function(element, className) {
+    if (!(element = $(element))) return;
+    return Element.classNames(element).add(className);
+  },
+
+  removeClassName: function(element, className) {
+    if (!(element = $(element))) return;
+    return Element.classNames(element).remove(className);
+  },
+
+  // removes whitespace-only text node children
+  cleanWhitespace: function(element) {
+    element = $(element);
+    for (var i = 0; i < element.childNodes.length; i++) {
+      var node = element.childNodes[i];
+      if (node.nodeType == 3 && !/\S/.test(node.nodeValue))
+        Element.remove(node);
+    }
+  },
+
+  empty: function(element) {
+    return $(element).innerHTML.match(/^\s*$/);
+  },
+
+  scrollTo: function(element) {
+    element = $(element);
+    var x = element.x ? element.x : element.offsetLeft,
+        y = element.y ? element.y : element.offsetTop;
+    window.scrollTo(x, y);
+  },
+
+  getStyle: function(element, style) {
+    element = $(element);
+    var value = element.style[style.camelize()];
+    if (!value) {
+      if (document.defaultView && document.defaultView.getComputedStyle) {
+        var css = document.defaultView.getComputedStyle(element, null);
+        value = css ? css.getPropertyValue(style) : null;
+      } else if (element.currentStyle) {
+        value = element.currentStyle[style.camelize()];
+      }
+    }
+
+    if (window.opera && ['left', 'top', 'right', 'bottom'].include(style))
+      if (Element.getStyle(element, 'position') == 'static') value = 'auto';
+
+    return value == 'auto' ? null : value;
+  },
+
+  setStyle: function(element, style) {
+    element = $(element);
+    for (name in style)
+      element.style[name.camelize()] = style[name];
+  },
+
+  getDimensions: function(element) {
+    element = $(element);
+    if (Element.getStyle(element, 'display') != 'none')
+      return {width: element.offsetWidth, height: element.offsetHeight};
+
+    // All *Width and *Height properties give 0 on elements with display none,
+    // so enable the element temporarily
+    var els = element.style;
+    var originalVisibility = els.visibility;
+    var originalPosition = els.position;
+    els.visibility = 'hidden';
+    els.position = 'absolute';
+    els.display = '';
+    var originalWidth = element.clientWidth;
+    var originalHeight = element.clientHeight;
+    els.display = 'none';
+    els.position = originalPosition;
+    els.visibility = originalVisibility;
+    return {width: originalWidth, height: originalHeight};
+  },
+
+  makePositioned: function(element) {
+    element = $(element);
+    var pos = Element.getStyle(element, 'position');
+    if (pos == 'static' || !pos) {
+      element._madePositioned = true;
+      element.style.position = 'relative';
+      // Opera returns the offset relative to the positioning context, when an
+      // element is position relative but top and left have not been defined
+      if (window.opera) {
+        element.style.top = 0;
+        element.style.left = 0;
+      }
+    }
+  },
+
+  undoPositioned: function(element) {
+    element = $(element);
+    if (element._madePositioned) {
+      element._madePositioned = undefined;
+      element.style.position =
+        element.style.top =
+        element.style.left =
+        element.style.bottom =
+        element.style.right = '';
+    }
+  },
+
+  makeClipping: function(element) {
+    element = $(element);
+    if (element._overflow) return;
+    element._overflow = element.style.overflow;
+    if ((Element.getStyle(element, 'overflow') || 'visible') != 'hidden')
+      element.style.overflow = 'hidden';
+  },
+
+  undoClipping: function(element) {
+    element = $(element);
+    if (element._overflow) return;
+    element.style.overflow = element._overflow;
+    element._overflow = undefined;
+  }
+});
+
+var Toggle = new Object();
+Toggle.display = Element.toggle;
+
+/*--------------------------------------------------------------------------*/
+
+Abstract.Insertion = function(adjacency) {
+  this.adjacency = adjacency;
+}
+
+Abstract.Insertion.prototype = {
+  initialize: function(element, content) {
+    this.element = $(element);
+    this.content = content.stripScripts();
+
+    if (this.adjacency && this.element.insertAdjacentHTML) {
+      try {
+        this.element.insertAdjacentHTML(this.adjacency, this.content);
+      } catch (e) {
+        if (this.element.tagName.toLowerCase() == 'tbody') {
+          this.insertContent(this.contentFromAnonymousTable());
+        } else {
+          throw e;
+        }
+      }
+    } else {
+      this.range = this.element.ownerDocument.createRange();
+      if (this.initializeRange) this.initializeRange();
+      this.insertContent([this.range.createContextualFragment(this.content)]);
+    }
+
+    setTimeout(function() {content.evalScripts()}, 10);
+  },
+
+  contentFromAnonymousTable: function() {
+    var div = document.createElement('div');
+    div.innerHTML = '<table><tbody>' + this.content + '</tbody></table>';
+    return $A(div.childNodes[0].childNodes[0].childNodes);
+  }
+}
+
+var Insertion = new Object();
+
+Insertion.Before = Class.create();
+Insertion.Before.prototype = Object.extend(new Abstract.Insertion('beforeBegin'), {
+  initializeRange: function() {
+    this.range.setStartBefore(this.element);
+  },
+
+  insertContent: function(fragments) {
+    fragments.each((function(fragment) {
+      this.element.parentNode.insertBefore(fragment, this.element);
+    }).bind(this));
+  }
+});
+
+Insertion.Top = Class.create();
+Insertion.Top.prototype = Object.extend(new Abstract.Insertion('afterBegin'), {
+  initializeRange: function() {
+    this.range.selectNodeContents(this.element);
+    this.range.collapse(true);
+  },
+
+  insertContent: function(fragments) {
+    fragments.reverse(false).each((function(fragment) {
+      this.element.insertBefore(fragment, this.element.firstChild);
+    }).bind(this));
+  }
+});
+
+Insertion.Bottom = Class.create();
+Insertion.Bottom.prototype = Object.extend(new Abstract.Insertion('beforeEnd'), {
+  initializeRange: function() {
+    this.range.selectNodeContents(this.element);
+    this.range.collapse(this.element);
+  },
+
+  insertContent: function(fragments) {
+    fragments.each((function(fragment) {
+      this.element.appendChild(fragment);
+    }).bind(this));
+  }
+});
+
+Insertion.After = Class.create();
+Insertion.After.prototype = Object.extend(new Abstract.Insertion('afterEnd'), {
+  initializeRange: function() {
+    this.range.setStartAfter(this.element);
+  },
+
+  insertContent: function(fragments) {
+    fragments.each((function(fragment) {
+      this.element.parentNode.insertBefore(fragment,
+        this.element.nextSibling);
+    }).bind(this));
+  }
+});
+
+/*--------------------------------------------------------------------------*/
+
+Element.ClassNames = Class.create();
+Element.ClassNames.prototype = {
+  initialize: function(element) {
+    this.element = $(element);
+  },
+
+  _each: function(iterator) {
+    this.element.className.split(/\s+/).select(function(name) {
+      return name.length > 0;
+    })._each(iterator);
+  },
+
+  set: function(className) {
+    this.element.className = className;
+  },
+
+  add: function(classNameToAdd) {
+    if (this.include(classNameToAdd)) return;
+    this.set(this.toArray().concat(classNameToAdd).join(' '));
+  },
+
+  remove: function(classNameToRemove) {
+    if (!this.include(classNameToRemove)) return;
+    this.set(this.select(function(className) {
+      return className != classNameToRemove;
+    }).join(' '));
+  },
+
+  toString: function() {
+    return this.toArray().join(' ');
+  }
+}
+
+Object.extend(Element.ClassNames.prototype, Enumerable);
+var Field = {
+  clear: function() {
+    for (var i = 0; i < arguments.length; i++)
+      $(arguments[i]).value = '';
+  },
+
+  focus: function(element) {
+    $(element).focus();
+  },
+
+  present: function() {
+    for (var i = 0; i < arguments.length; i++)
+      if ($(arguments[i]).value == '') return false;
+    return true;
+  },
+
+  select: function(element) {
+    $(element).select();
+  },
+
+  activate: function(element) {
+    element = $(element);
+    element.focus();
+    if (element.select)
+      element.select();
+  }
+}
+
+/*--------------------------------------------------------------------------*/
+
+var Form = {
+  serialize: function(form) {
+    var elements = Form.getElements($(form));
+    var queryComponents = new Array();
+
+    for (var i = 0; i < elements.length; i++) {
+      var queryComponent = Form.Element.serialize(elements[i]);
+      if (queryComponent)
+        queryComponents.push(queryComponent);
+    }
+
+    return queryComponents.join('&');
+  },
+
+  getElements: function(form) {
+    form = $(form);
+    var elements = new Array();
+
+    for (tagName in Form.Element.Serializers) {
+      var tagElements = form.getElementsByTagName(tagName);
+      for (var j = 0; j < tagElements.length; j++)
+        elements.push(tagElements[j]);
+    }
+    return elements;
+  },
+
+  getInputs: function(form, typeName, name) {
+    form = $(form);
+    var inputs = form.getElementsByTagName('input');
+
+    if (!typeName && !name)
+      return inputs;
+
+    var matchingInputs = new Array();
+    for (var i = 0; i < inputs.length; i++) {
+      var input = inputs[i];
+      if ((typeName && input.type != typeName) ||
+          (name && input.name != name))
+        continue;
+      matchingInputs.push(input);
+    }
+
+    return matchingInputs;
+  },
+
+  disable: function(form) {
+    var elements = Form.getElements(form);
+    for (var i = 0; i < elements.length; i++) {
+      var element = elements[i];
+      element.blur();
+      element.disabled = 'true';
+    }
+  },
+
+  enable: function(form) {
+    var elements = Form.getElements(form);
+    for (var i = 0; i < elements.length; i++) {
+      var element = elements[i];
+      element.disabled = '';
+    }
+  },
+
+  findFirstElement: function(form) {
+    return Form.getElements(form).find(function(element) {
+      return element.type != 'hidden' && !element.disabled &&
+        ['input', 'select', 'textarea'].include(element.tagName.toLowerCase());
+    });
+  },
+
+  focusFirstElement: function(form) {
+    Field.activate(Form.findFirstElement(form));
+  },
+
+  reset: function(form) {
+    $(form).reset();
+  }
+}
+
+Form.Element = {
+  serialize: function(element) {
+    element = $(element);
+    var method = element.tagName.toLowerCase();
+    var parameter = Form.Element.Serializers[method](element);
+
+    if (parameter) {
+      var key = encodeURIComponent(parameter[0]);
+      if (key.length == 0) return;
+
+      if (parameter[1].constructor != Array)
+        parameter[1] = [parameter[1]];
+
+      return parameter[1].map(function(value) {
+        return key + '=' + encodeURIComponent(value);
+      }).join('&');
+    }
+  },
+
+  getValue: function(element) {
+    element = $(element);
+    var method = element.tagName.toLowerCase();
+    var parameter = Form.Element.Serializers[method](element);
+
+    if (parameter)
+      return parameter[1];
+  }
+}
+
+Form.Element.Serializers = {
+  input: function(element) {
+    switch (element.type.toLowerCase()) {
+      case 'submit':
+      case 'hidden':
+      case 'password':
+      case 'text':
+        return Form.Element.Serializers.textarea(element);
+      case 'checkbox':
+      case 'radio':
+        return Form.Element.Serializers.inputSelector(element);
+    }
+    return false;
+  },
+
+  inputSelector: function(element) {
+    if (element.checked)
+      return [element.name, element.value];
+  },
+
+  textarea: function(element) {
+    return [element.name, element.value];
+  },
+
+  select: function(element) {
+    return Form.Element.Serializers[element.type == 'select-one' ?
+      'selectOne' : 'selectMany'](element);
+  },
+
+  selectOne: function(element) {
+    var value = '', opt, index = element.selectedIndex;
+    if (index >= 0) {
+      opt = element.options[index];
+      value = opt.value;
+      if (!value && !('value' in opt))
+        value = opt.text;
+    }
+    return [element.name, value];
+  },
+
+  selectMany: function(element) {
+    var value = new Array();
+    for (var i = 0; i < element.length; i++) {
+      var opt = element.options[i];
+      if (opt.selected) {
+        var optValue = opt.value;
+        if (!optValue && !('value' in opt))
+          optValue = opt.text;
+        value.push(optValue);
+      }
+    }
+    return [element.name, value];
+  }
+}
+
+/*--------------------------------------------------------------------------*/
+
+var $F = Form.Element.getValue;
+
+/*--------------------------------------------------------------------------*/
+
+Abstract.TimedObserver = function() {}
+Abstract.TimedObserver.prototype = {
+  initialize: function(element, frequency, callback) {
+    this.frequency = frequency;
+    this.element   = $(element);
+    this.callback  = callback;
+
+    this.lastValue = this.getValue();
+    this.registerCallback();
+  },
+
+  registerCallback: function() {
+    setInterval(this.onTimerEvent.bind(this), this.frequency * 1000);
+  },
+
+  onTimerEvent: function() {
+    var value = this.getValue();
+    if (this.lastValue != value) {
+      this.callback(this.element, value);
+      this.lastValue = value;
+    }
+  }
+}
+
+Form.Element.Observer = Class.create();
+Form.Element.Observer.prototype = Object.extend(new Abstract.TimedObserver(), {
+  getValue: function() {
+    return Form.Element.getValue(this.element);
+  }
+});
+
+Form.Observer = Class.create();
+Form.Observer.prototype = Object.extend(new Abstract.TimedObserver(), {
+  getValue: function() {
+    return Form.serialize(this.element);
+  }
+});
+
+/*--------------------------------------------------------------------------*/
+
+Abstract.EventObserver = function() {}
+Abstract.EventObserver.prototype = {
+  initialize: function(element, callback) {
+    this.element  = $(element);
+    this.callback = callback;
+
+    this.lastValue = this.getValue();
+    if (this.element.tagName.toLowerCase() == 'form')
+      this.registerFormCallbacks();
+    else
+      this.registerCallback(this.element);
+  },
+
+  onElementEvent: function() {
+    var value = this.getValue();
+    if (this.lastValue != value) {
+      this.callback(this.element, value);
+      this.lastValue = value;
+    }
+  },
+
+  registerFormCallbacks: function() {
+    var elements = Form.getElements(this.element);
+    for (var i = 0; i < elements.length; i++)
+      this.registerCallback(elements[i]);
+  },
+
+  registerCallback: function(element) {
+    if (element.type) {
+      switch (element.type.toLowerCase()) {
+        case 'checkbox':
+        case 'radio':
+          Event.observe(element, 'click', this.onElementEvent.bind(this));
+          break;
+        case 'password':
+        case 'text':
+        case 'textarea':
+        case 'select-one':
+        case 'select-multiple':
+          Event.observe(element, 'change', this.onElementEvent.bind(this));
+          break;
+      }
+    }
+  }
+}
+
+Form.Element.EventObserver = Class.create();
+Form.Element.EventObserver.prototype = Object.extend(new Abstract.EventObserver(), {
+  getValue: function() {
+    return Form.Element.getValue(this.element);
+  }
+});
+
+Form.EventObserver = Class.create();
+Form.EventObserver.prototype = Object.extend(new Abstract.EventObserver(), {
+  getValue: function() {
+    return Form.serialize(this.element);
+  }
+});
+if (!window.Event) {
+  var Event = new Object();
+}
+
+Object.extend(Event, {
+  KEY_BACKSPACE: 8,
+  KEY_TAB:       9,
+  KEY_RETURN:   13,
+  KEY_ESC:      27,
+  KEY_LEFT:     37,
+  KEY_UP:       38,
+  KEY_RIGHT:    39,
+  KEY_DOWN:     40,
+  KEY_DELETE:   46,
+
+  element: function(event) {
+    return event.target || event.srcElement;
+  },
+
+  isLeftClick: function(event) {
+    return (((event.which) && (event.which == 1)) ||
+            ((event.button) && (event.button == 1)));
+  },
+
+  pointerX: function(event) {
+    return event.pageX || (event.clientX +
+      (document.documentElement.scrollLeft || document.body.scrollLeft));
+  },
+
+  pointerY: function(event) {
+    return event.pageY || (event.clientY +
+      (document.documentElement.scrollTop || document.body.scrollTop));
+  },
+
+  stop: function(event) {
+    if (event.preventDefault) {
+      event.preventDefault();
+      event.stopPropagation();
+    } else {
+      event.returnValue = false;
+      event.cancelBubble = true;
+    }
+  },
+
+  // find the first node with the given tagName, starting from the
+  // node the event was triggered on; traverses the DOM upwards
+  findElement: function(event, tagName) {
+    var element = Event.element(event);
+    while (element.parentNode && (!element.tagName ||
+        (element.tagName.toUpperCase() != tagName.toUpperCase())))
+      element = element.parentNode;
+    return element;
+  },
+
+  observers: false,
+
+  _observeAndCache: function(element, name, observer, useCapture) {
+    if (!this.observers) this.observers = [];
+    if (element.addEventListener) {
+      this.observers.push([element, name, observer, useCapture]);
+      element.addEventListener(name, observer, useCapture);
+    } else if (element.attachEvent) {
+      this.observers.push([element, name, observer, useCapture]);
+      element.attachEvent('on' + name, observer);
+    }
+  },
+
+  unloadCache: function() {
+    if (!Event.observers) return;
+    for (var i = 0; i < Event.observers.length; i++) {
+      Event.stopObserving.apply(this, Event.observers[i]);
+      Event.observers[i][0] = null;
+    }
+    Event.observers = false;
+  },
+
+  observe: function(element, name, observer, useCapture) {
+    var element = $(element);
+    useCapture = useCapture || false;
+
+    if (name == 'keypress' &&
+        (navigator.appVersion.match(/Konqueror|Safari|KHTML/)
+        || element.attachEvent))
+      name = 'keydown';
+
+    this._observeAndCache(element, name, observer, useCapture);
+  },
+
+  stopObserving: function(element, name, observer, useCapture) {
+    var element = $(element);
+    useCapture = useCapture || false;
+
+    if (name == 'keypress' &&
+        (navigator.appVersion.match(/Konqueror|Safari|KHTML/)
+        || element.detachEvent))
+      name = 'keydown';
+
+    if (element.removeEventListener) {
+      element.removeEventListener(name, observer, useCapture);
+    } else if (element.detachEvent) {
+      element.detachEvent('on' + name, observer);
+    }
+  }
+});
+
+/* prevent memory leaks in IE */
+Event.observe(window, 'unload', Event.unloadCache, false);
+var Position = {
+  // set to true if needed, warning: firefox performance problems
+  // NOT neeeded for page scrolling, only if draggable contained in
+  // scrollable elements
+  includeScrollOffsets: false,
+
+  // must be called before calling withinIncludingScrolloffset, every time the
+  // page is scrolled
+  prepare: function() {
+    this.deltaX =  window.pageXOffset
+                || document.documentElement.scrollLeft
+                || document.body.scrollLeft
+                || 0;
+    this.deltaY =  window.pageYOffset
+                || document.documentElement.scrollTop
+                || document.body.scrollTop
+                || 0;
+  },
+
+  realOffset: function(element) {
+    var valueT = 0, valueL = 0;
+    do {
+      valueT += element.scrollTop  || 0;
+      valueL += element.scrollLeft || 0;
+      element = element.parentNode;
+    } while (element);
+    return [valueL, valueT];
+  },
+
+  cumulativeOffset: function(element) {
+    var valueT = 0, valueL = 0;
+    do {
+      valueT += element.offsetTop  || 0;
+      valueL += element.offsetLeft || 0;
+      element = element.offsetParent;
+    } while (element);
+    return [valueL, valueT];
+  },
+
+  positionedOffset: function(element) {
+    var valueT = 0, valueL = 0;
+    do {
+      valueT += element.offsetTop  || 0;
+      valueL += element.offsetLeft || 0;
+      element = element.offsetParent;
+      if (element) {
+        p = Element.getStyle(element, 'position');
+        if (p == 'relative' || p == 'absolute') break;
+      }
+    } while (element);
+    return [valueL, valueT];
+  },
+
+  offsetParent: function(element) {
+    if (element.offsetParent) return element.offsetParent;
+    if (element == document.body) return element;
+
+    while ((element = element.parentNode) && element != document.body)
+      if (Element.getStyle(element, 'position') != 'static')
+        return element;
+
+    return document.body;
+  },
+
+  // caches x/y coordinate pair to use with overlap
+  within: function(element, x, y) {
+    if (this.includeScrollOffsets)
+      return this.withinIncludingScrolloffsets(element, x, y);
+    this.xcomp = x;
+    this.ycomp = y;
+    this.offset = this.cumulativeOffset(element);
+
+    return (y >= this.offset[1] &&
+            y <  this.offset[1] + element.offsetHeight &&
+            x >= this.offset[0] &&
+            x <  this.offset[0] + element.offsetWidth);
+  },
+
+  withinIncludingScrolloffsets: function(element, x, y) {
+    var offsetcache = this.realOffset(element);
+
+    this.xcomp = x + offsetcache[0] - this.deltaX;
+    this.ycomp = y + offsetcache[1] - this.deltaY;
+    this.offset = this.cumulativeOffset(element);
+
+    return (this.ycomp >= this.offset[1] &&
+            this.ycomp <  this.offset[1] + element.offsetHeight &&
+            this.xcomp >= this.offset[0] &&
+            this.xcomp <  this.offset[0] + element.offsetWidth);
+  },
+
+  // within must be called directly before
+  overlap: function(mode, element) {
+    if (!mode) return 0;
+    if (mode == 'vertical')
+      return ((this.offset[1] + element.offsetHeight) - this.ycomp) /
+        element.offsetHeight;
+    if (mode == 'horizontal')
+      return ((this.offset[0] + element.offsetWidth) - this.xcomp) /
+        element.offsetWidth;
+  },
+
+  clone: function(source, target) {
+    source = $(source);
+    target = $(target);
+    target.style.position = 'absolute';
+    var offsets = this.cumulativeOffset(source);
+    target.style.top    = offsets[1] + 'px';
+    target.style.left   = offsets[0] + 'px';
+    target.style.width  = source.offsetWidth + 'px';
+    target.style.height = source.offsetHeight + 'px';
+  },
+
+  page: function(forElement) {
+    var valueT = 0, valueL = 0;
+
+    var element = forElement;
+    do {
+      valueT += element.offsetTop  || 0;
+      valueL += element.offsetLeft || 0;
+
+      // Safari fix
+      if (element.offsetParent==document.body)
+        if (Element.getStyle(element,'position')=='absolute') break;
+
+    } while (element = element.offsetParent);
+
+    element = forElement;
+    do {
+      valueT -= element.scrollTop  || 0;
+      valueL -= element.scrollLeft || 0;
+    } while (element = element.parentNode);
+
+    return [valueL, valueT];
+  },
+
+  clone: function(source, target) {
+    var options = Object.extend({
+      setLeft:    true,
+      setTop:     true,
+      setWidth:   true,
+      setHeight:  true,
+      offsetTop:  0,
+      offsetLeft: 0
+    }, arguments[2] || {})
+
+    // find page position of source
+    source = $(source);
+    var p = Position.page(source);
+
+    // find coordinate system to use
+    target = $(target);
+    var delta = [0, 0];
+    var parent = null;
+    // delta [0,0] will do fine with position: fixed elements,
+    // position:absolute needs offsetParent deltas
+    if (Element.getStyle(target,'position') == 'absolute') {
+      parent = Position.offsetParent(target);
+      delta = Position.page(parent);
+    }
+
+    // correct by body offsets (fixes Safari)
+    if (parent == document.body) {
+      delta[0] -= document.body.offsetLeft;
+      delta[1] -= document.body.offsetTop;
+    }
+
+    // set position
+    if(options.setLeft)   target.style.left  = (p[0] - delta[0] + options.offsetLeft) + 'px';
+    if(options.setTop)    target.style.top   = (p[1] - delta[1] + options.offsetTop) + 'px';
+    if(options.setWidth)  target.style.width = source.offsetWidth + 'px';
+    if(options.setHeight) target.style.height = source.offsetHeight + 'px';
+  },
+
+  absolutize: function(element) {
+    element = $(element);
+    if (element.style.position == 'absolute') return;
+    Position.prepare();
+
+    var offsets = Position.positionedOffset(element);
+    var top     = offsets[1];
+    var left    = offsets[0];
+    var width   = element.clientWidth;
+    var height  = element.clientHeight;
+
+    element._originalLeft   = left - parseFloat(element.style.left  || 0);
+    element._originalTop    = top  - parseFloat(element.style.top || 0);
+    element._originalWidth  = element.style.width;
+    element._originalHeight = element.style.height;
+
+    element.style.position = 'absolute';
+    element.style.top    = top + 'px';;
+    element.style.left   = left + 'px';;
+    element.style.width  = width + 'px';;
+    element.style.height = height + 'px';;
+  },
+
+  relativize: function(element) {
+    element = $(element);
+    if (element.style.position == 'relative') return;
+    Position.prepare();
+
+    element.style.position = 'relative';
+    var top  = parseFloat(element.style.top  || 0) - (element._originalTop || 0);
+    var left = parseFloat(element.style.left || 0) - (element._originalLeft || 0);
+
+    element.style.top    = top + 'px';
+    element.style.left   = left + 'px';
+    element.style.height = element._originalHeight;
+    element.style.width  = element._originalWidth;
+  }
+}
+
+// Safari returns margins on body which is incorrect if the child is absolutely
+// positioned.  For performance reasons, redefine Position.cumulativeOffset for
+// KHTML/WebKit only.
+if (/Konqueror|Safari|KHTML/.test(navigator.userAgent)) {
+  Position.cumulativeOffset = function(element) {
+    var valueT = 0, valueL = 0;
+    do {
+      valueT += element.offsetTop  || 0;
+      valueL += element.offsetLeft || 0;
+      if (element.offsetParent == document.body)
+        if (Element.getStyle(element, 'position') == 'absolute') break;
+
+      element = element.offsetParent;
+    } while (element);
+
+    return [valueL, valueT];
+  }
+}
\ No newline at end of file
diff --git a/vendor/gems/ruby-openid-2.1.4/examples/rails_openid/public/robots.txt b/vendor/gems/ruby-openid-2.1.4/examples/rails_openid/public/robots.txt
new file mode 100644 (file)
index 0000000..4ab9e89
--- /dev/null
@@ -0,0 +1 @@
+# See http://www.robotstxt.org/wc/norobots.html for documentation on how to use the robots.txt file
\ No newline at end of file
diff --git a/vendor/gems/ruby-openid-2.1.4/examples/rails_openid/script/about b/vendor/gems/ruby-openid-2.1.4/examples/rails_openid/script/about
new file mode 100644 (file)
index 0000000..7b07d46
--- /dev/null
@@ -0,0 +1,3 @@
+#!/usr/bin/env ruby
+require File.dirname(__FILE__) + '/../config/boot'
+require 'commands/about'
\ No newline at end of file
diff --git a/vendor/gems/ruby-openid-2.1.4/examples/rails_openid/script/breakpointer b/vendor/gems/ruby-openid-2.1.4/examples/rails_openid/script/breakpointer
new file mode 100644 (file)
index 0000000..64af76e
--- /dev/null
@@ -0,0 +1,3 @@
+#!/usr/bin/env ruby
+require File.dirname(__FILE__) + '/../config/boot'
+require 'commands/breakpointer'
\ No newline at end of file
diff --git a/vendor/gems/ruby-openid-2.1.4/examples/rails_openid/script/console b/vendor/gems/ruby-openid-2.1.4/examples/rails_openid/script/console
new file mode 100644 (file)
index 0000000..42f28f7
--- /dev/null
@@ -0,0 +1,3 @@
+#!/usr/bin/env ruby
+require File.dirname(__FILE__) + '/../config/boot'
+require 'commands/console'
\ No newline at end of file
diff --git a/vendor/gems/ruby-openid-2.1.4/examples/rails_openid/script/destroy b/vendor/gems/ruby-openid-2.1.4/examples/rails_openid/script/destroy
new file mode 100644 (file)
index 0000000..fa0e6fc
--- /dev/null
@@ -0,0 +1,3 @@
+#!/usr/bin/env ruby
+require File.dirname(__FILE__) + '/../config/boot'
+require 'commands/destroy'
\ No newline at end of file
diff --git a/vendor/gems/ruby-openid-2.1.4/examples/rails_openid/script/generate b/vendor/gems/ruby-openid-2.1.4/examples/rails_openid/script/generate
new file mode 100644 (file)
index 0000000..ef976e0
--- /dev/null
@@ -0,0 +1,3 @@
+#!/usr/bin/env ruby
+require File.dirname(__FILE__) + '/../config/boot'
+require 'commands/generate'
\ No newline at end of file
diff --git a/vendor/gems/ruby-openid-2.1.4/examples/rails_openid/script/performance/benchmarker b/vendor/gems/ruby-openid-2.1.4/examples/rails_openid/script/performance/benchmarker
new file mode 100644 (file)
index 0000000..c842d35
--- /dev/null
@@ -0,0 +1,3 @@
+#!/usr/bin/env ruby
+require File.dirname(__FILE__) + '/../../config/boot'
+require 'commands/performance/benchmarker'
diff --git a/vendor/gems/ruby-openid-2.1.4/examples/rails_openid/script/performance/profiler b/vendor/gems/ruby-openid-2.1.4/examples/rails_openid/script/performance/profiler
new file mode 100644 (file)
index 0000000..d855ac8
--- /dev/null
@@ -0,0 +1,3 @@
+#!/usr/bin/env ruby
+require File.dirname(__FILE__) + '/../../config/boot'
+require 'commands/performance/profiler'
diff --git a/vendor/gems/ruby-openid-2.1.4/examples/rails_openid/script/plugin b/vendor/gems/ruby-openid-2.1.4/examples/rails_openid/script/plugin
new file mode 100644 (file)
index 0000000..26ca64c
--- /dev/null
@@ -0,0 +1,3 @@
+#!/usr/bin/env ruby
+require File.dirname(__FILE__) + '/../config/boot'
+require 'commands/plugin'
\ No newline at end of file
diff --git a/vendor/gems/ruby-openid-2.1.4/examples/rails_openid/script/process/reaper b/vendor/gems/ruby-openid-2.1.4/examples/rails_openid/script/process/reaper
new file mode 100644 (file)
index 0000000..c77f045
--- /dev/null
@@ -0,0 +1,3 @@
+#!/usr/bin/env ruby
+require File.dirname(__FILE__) + '/../../config/boot'
+require 'commands/process/reaper'
diff --git a/vendor/gems/ruby-openid-2.1.4/examples/rails_openid/script/process/spawner b/vendor/gems/ruby-openid-2.1.4/examples/rails_openid/script/process/spawner
new file mode 100644 (file)
index 0000000..7118f39
--- /dev/null
@@ -0,0 +1,3 @@
+#!/usr/bin/env ruby
+require File.dirname(__FILE__) + '/../../config/boot'
+require 'commands/process/spawner'
diff --git a/vendor/gems/ruby-openid-2.1.4/examples/rails_openid/script/process/spinner b/vendor/gems/ruby-openid-2.1.4/examples/rails_openid/script/process/spinner
new file mode 100644 (file)
index 0000000..6816b32
--- /dev/null
@@ -0,0 +1,3 @@
+#!/usr/bin/env ruby
+require File.dirname(__FILE__) + '/../../config/boot'
+require 'commands/process/spinner'
diff --git a/vendor/gems/ruby-openid-2.1.4/examples/rails_openid/script/runner b/vendor/gems/ruby-openid-2.1.4/examples/rails_openid/script/runner
new file mode 100644 (file)
index 0000000..ccc30f9
--- /dev/null
@@ -0,0 +1,3 @@
+#!/usr/bin/env ruby
+require File.dirname(__FILE__) + '/../config/boot'
+require 'commands/runner'
\ No newline at end of file
diff --git a/vendor/gems/ruby-openid-2.1.4/examples/rails_openid/script/server b/vendor/gems/ruby-openid-2.1.4/examples/rails_openid/script/server
new file mode 100644 (file)
index 0000000..dfabcb8
--- /dev/null
@@ -0,0 +1,3 @@
+#!/usr/bin/env ruby
+require File.dirname(__FILE__) + '/../config/boot'
+require 'commands/server'
\ No newline at end of file
diff --git a/vendor/gems/ruby-openid-2.1.4/examples/rails_openid/test/functional/login_controller_test.rb b/vendor/gems/ruby-openid-2.1.4/examples/rails_openid/test/functional/login_controller_test.rb
new file mode 100644 (file)
index 0000000..a21d438
--- /dev/null
@@ -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
diff --git a/vendor/gems/ruby-openid-2.1.4/examples/rails_openid/test/functional/server_controller_test.rb b/vendor/gems/ruby-openid-2.1.4/examples/rails_openid/test/functional/server_controller_test.rb
new file mode 100644 (file)
index 0000000..7afd69e
--- /dev/null
@@ -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
diff --git a/vendor/gems/ruby-openid-2.1.4/examples/rails_openid/test/test_helper.rb b/vendor/gems/ruby-openid-2.1.4/examples/rails_openid/test/test_helper.rb
new file mode 100644 (file)
index 0000000..a299c7f
--- /dev/null
@@ -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
diff --git a/vendor/gems/ruby-openid-2.1.4/lib/hmac/hmac.rb b/vendor/gems/ruby-openid-2.1.4/lib/hmac/hmac.rb
new file mode 100644 (file)
index 0000000..e8bfa42
--- /dev/null
@@ -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
diff --git a/vendor/gems/ruby-openid-2.1.4/lib/hmac/sha1.rb b/vendor/gems/ruby-openid-2.1.4/lib/hmac/sha1.rb
new file mode 100644 (file)
index 0000000..d2f0088
--- /dev/null
@@ -0,0 +1,11 @@
+require 'hmac/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
diff --git a/vendor/gems/ruby-openid-2.1.4/lib/hmac/sha2.rb b/vendor/gems/ruby-openid-2.1.4/lib/hmac/sha2.rb
new file mode 100644 (file)
index 0000000..0412ba4
--- /dev/null
@@ -0,0 +1,25 @@
+require 'hmac/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
diff --git a/vendor/gems/ruby-openid-2.1.4/lib/openid.rb b/vendor/gems/ruby-openid-2.1.4/lib/openid.rb
new file mode 100644 (file)
index 0000000..0a56434
--- /dev/null
@@ -0,0 +1,20 @@
+# Copyright 2006-2007 JanRain, Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you
+# may not use this file except in compliance with the License. You may
+# obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# permissions and limitations under the License.
+
+module OpenID
+  VERSION = "2.1.4"
+end
+
+require "openid/consumer"
+require 'openid/server'
diff --git a/vendor/gems/ruby-openid-2.1.4/lib/openid/association.rb b/vendor/gems/ruby-openid-2.1.4/lib/openid/association.rb
new file mode 100644 (file)
index 0000000..fd2cd59
--- /dev/null
@@ -0,0 +1,249 @@
+require "openid/kvform"
+require "openid/util"
+require "openid/cryptutil"
+require "openid/message"
+
+module OpenID
+
+  def self.get_secret_size(assoc_type)
+    if assoc_type == 'HMAC-SHA1'
+      return 20
+    elsif assoc_type == 'HMAC-SHA256'
+      return 32
+    else
+      raise ArgumentError("Unsupported association type: #{assoc_type}")
+    end
+  end
+
+  # An Association holds the shared secret between a relying party and
+  # an OpenID provider.
+  class Association
+    attr_reader :handle, :secret, :issued, :lifetime, :assoc_type
+
+    FIELD_ORDER =
+      [:version, :handle, :secret, :issued, :lifetime, :assoc_type,]
+
+    # Load a serialized Association
+    def self.deserialize(serialized)
+      parsed = Util.kv_to_seq(serialized)
+      parsed_fields = parsed.map{|k, v| k.to_sym}
+      if parsed_fields != FIELD_ORDER
+          raise ProtocolError, 'Unexpected fields in serialized association'\
+          " (Expected #{FIELD_ORDER.inspect}, got #{parsed_fields.inspect})"
+      end
+      version, handle, secret64, issued_s, lifetime_s, assoc_type =
+        parsed.map {|field, value| value}
+      if version != '2'
+        raise ProtocolError, "Attempted to deserialize unsupported version "\
+                             "(#{parsed[0][1].inspect})"
+      end
+
+      self.new(handle,
+               Util.from_base64(secret64),
+               Time.at(issued_s.to_i),
+               lifetime_s.to_i,
+               assoc_type)
+    end
+
+    # Create an Association with an issued time of now
+    def self.from_expires_in(expires_in, handle, secret, assoc_type)
+      issued = Time.now
+      self.new(handle, secret, issued, expires_in, assoc_type)
+    end
+
+    def initialize(handle, secret, issued, lifetime, assoc_type)
+      @handle = handle
+      @secret = secret
+      @issued = issued
+      @lifetime = lifetime
+      @assoc_type = assoc_type
+    end
+
+    # Serialize the association to a form that's consistent across
+    # JanRain OpenID libraries.
+    def serialize
+      data = {
+        :version => '2',
+        :handle => handle,
+        :secret => Util.to_base64(secret),
+        :issued => issued.to_i.to_s,
+        :lifetime => lifetime.to_i.to_s,
+        :assoc_type => assoc_type,
+      }
+
+      Util.assert(data.length == FIELD_ORDER.length)
+
+      pairs = FIELD_ORDER.map{|field| [field.to_s, data[field]]}
+      return Util.seq_to_kv(pairs, strict=true)
+    end
+
+    # The number of seconds until this association expires
+    def expires_in(now=nil)
+      if now.nil?
+        now = Time.now.to_i
+      else
+        now = now.to_i
+      end
+      time_diff = (issued.to_i + lifetime) - now
+      if time_diff < 0
+        return 0
+      else
+        return time_diff
+      end
+    end
+
+    # Generate a signature for a sequence of [key, value] pairs
+    def sign(pairs)
+      kv = Util.seq_to_kv(pairs)
+      case assoc_type
+      when 'HMAC-SHA1'
+        CryptUtil.hmac_sha1(@secret, kv)
+      when 'HMAC-SHA256'
+        CryptUtil.hmac_sha256(@secret, kv)
+      else
+        raise ProtocolError, "Association has unknown type: "\
+          "#{assoc_type.inspect}"
+      end
+    end
+
+    # Generate the list of pairs that form the signed elements of the
+    # given message
+    def make_pairs(message)
+      signed = message.get_arg(OPENID_NS, 'signed')
+      if signed.nil?
+        raise ProtocolError, 'Missing signed list'
+      end
+      signed_fields = signed.split(',', -1)
+      data = message.to_post_args
+      signed_fields.map {|field| [field, data.fetch('openid.'+field,'')] }
+    end
+
+    # Return whether the message's signature passes
+    def check_message_signature(message)
+      message_sig = message.get_arg(OPENID_NS, 'sig')
+      if message_sig.nil?
+        raise ProtocolError, "#{message} has no sig."
+      end
+      calculated_sig = get_message_signature(message)
+      return calculated_sig == message_sig
+    end
+
+    # Get the signature for this message
+    def get_message_signature(message)
+      Util.to_base64(sign(make_pairs(message)))
+    end
+
+    def ==(other)
+      (other.class == self.class and 
+       other.handle == self.handle and
+       other.secret == self.secret and
+
+       # The internals of the time objects seemed to differ
+       # in an opaque way when serializing/unserializing.
+       # I don't think this will be a problem.
+       other.issued.to_i == self.issued.to_i and
+
+       other.lifetime == self.lifetime and
+       other.assoc_type == self.assoc_type)
+    end
+
+    # Add a signature (and a signed list) to a message.
+    def sign_message(message)
+      if (message.has_key?(OPENID_NS, 'sig') or
+          message.has_key?(OPENID_NS, 'signed'))
+        raise ArgumentError, 'Message already has signed list or signature'
+      end
+
+      extant_handle = message.get_arg(OPENID_NS, 'assoc_handle')
+      if extant_handle and extant_handle != self.handle
+        raise ArgumentError, "Message has a different association handle"
+      end
+
+      signed_message = message.copy()
+      signed_message.set_arg(OPENID_NS, 'assoc_handle', self.handle)
+      message_keys = signed_message.to_post_args.keys()
+
+      signed_list = []
+      message_keys.each { |k|
+        if k.starts_with?('openid.')
+          signed_list << k[7..-1]
+        end
+      }
+
+      signed_list << 'signed'
+      signed_list.sort!
+
+      signed_message.set_arg(OPENID_NS, 'signed', signed_list.join(','))
+      sig = get_message_signature(signed_message)
+      signed_message.set_arg(OPENID_NS, 'sig', sig)
+      return signed_message
+    end
+  end
+
+  class AssociationNegotiator
+    attr_reader :allowed_types
+
+    def self.get_session_types(assoc_type)
+      case assoc_type
+      when 'HMAC-SHA1'
+        ['DH-SHA1', 'no-encryption']
+      when 'HMAC-SHA256'
+        ['DH-SHA256', 'no-encryption']
+      else
+        raise ProtocolError, "Unknown association type #{assoc_type.inspect}"
+      end
+    end
+
+    def self.check_session_type(assoc_type, session_type)
+      if !get_session_types(assoc_type).include?(session_type)
+        raise ProtocolError, "Session type #{session_type.inspect} not "\
+                             "valid for association type #{assoc_type.inspect}"
+      end
+    end
+
+    def initialize(allowed_types)
+      self.allowed_types=(allowed_types)
+    end
+
+    def copy
+      Marshal.load(Marshal.dump(self))
+    end
+
+    def allowed_types=(allowed_types)
+      allowed_types.each do |assoc_type, session_type|
+        self.class.check_session_type(assoc_type, session_type)
+      end
+      @allowed_types = allowed_types
+    end
+
+    def add_allowed_type(assoc_type, session_type=nil)
+      if session_type.nil?
+        session_types = self.class.get_session_types(assoc_type)
+      else
+        self.class.check_session_type(assoc_type, session_type)
+        session_types = [session_type]
+      end
+      for session_type in session_types do
+        @allowed_types << [assoc_type, session_type]
+      end
+    end
+
+    def allowed?(assoc_type, session_type)
+      @allowed_types.include?([assoc_type, session_type])
+    end
+
+    def get_allowed_type
+      @allowed_types.empty? ? nil : @allowed_types[0]
+    end
+  end
+
+  DefaultNegotiator =
+    AssociationNegotiator.new([['HMAC-SHA1', 'DH-SHA1'],
+                               ['HMAC-SHA1', 'no-encryption'],
+                               ['HMAC-SHA256', 'DH-SHA256'],
+                               ['HMAC-SHA256', 'no-encryption']])
+
+  EncryptedNegotiator =
+    AssociationNegotiator.new([['HMAC-SHA1', 'DH-SHA1'],
+                               ['HMAC-SHA256', 'DH-SHA256']])
+end
diff --git a/vendor/gems/ruby-openid-2.1.4/lib/openid/consumer.rb b/vendor/gems/ruby-openid-2.1.4/lib/openid/consumer.rb
new file mode 100644 (file)
index 0000000..afe025a
--- /dev/null
@@ -0,0 +1,395 @@
+require "openid/consumer/idres.rb"
+require "openid/consumer/checkid_request.rb"
+require "openid/consumer/associationmanager.rb"
+require "openid/consumer/responses.rb"
+require "openid/consumer/discovery_manager"
+require "openid/consumer/discovery"
+require "openid/message"
+require "openid/yadis/discovery"
+require "openid/store/nonce"
+
+module OpenID
+  # OpenID support for Relying Parties (aka Consumers).
+  #
+  # This module documents the main interface with the OpenID consumer
+  # library.  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
+  # Consumer instance.
+  #
+  # = OVERVIEW
+  #
+  # The OpenID identity verification process most commonly uses 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 provider using
+  #    the Yadis protocol.
+  #
+  # 3. The consumer site sends the browser a redirect to the OpenID
+  #    provider.  This is the authentication request as described in
+  #    the OpenID specification.
+  #
+  # 4. The OpenID provider's site sends the browser a redirect back to
+  #    the consumer site.  This redirect contains the provider'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.
+  #
+  # = LIBRARY DESIGN
+  #
+  # This consumer library is designed with that 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 this module, which contains
+  # the interface to actually use this library.  The second is
+  # openid/store/interface.rb, which describes the interface to use if
+  # you need to create a custom method for storing the state this
+  # library needs to maintain between requests.
+  #
+  # In general, the second part is less important for users of the
+  # library to know about, as several implementations are provided
+  # which cover a wide variety of situations in which consumers may
+  # use the library.
+  #
+  # The Consumer class has methods corresponding to the actions
+  # necessary in each of steps 2, 3, and 4 described in the overview.
+  # Use of this library should be as easy as creating an Consumer
+  # instance and calling the methods appropriate for the action the
+  # site wants to take.
+  #
+  # This library automatically detects which version of the OpenID
+  # protocol should be used for a transaction and constructs the
+  # proper requests and responses.  Users of this library do not need
+  # to worry about supporting multiple protocol versions; the library
+  # supports them implicitly.  Depending on the version of the
+  # protocol in use, the OpenID transaction may be more secure.  See
+  # the OpenID specifications for more information.
+  #
+  # = SESSIONS, STORES, AND STATELESS MODE
+  #
+  # The Consumer object keeps track of two types of state:
+  #
+  # 1. State of the user's current authentication attempt.  Things
+  #    like the identity URL, the list of endpoints discovered for
+  #    that URL, and in case where some endpoints are unreachable, the
+  #    list of endpoints already tried.  This state needs to be held
+  #    from Consumer.begin() to Consumer.complete(), but it is only
+  #    applicable to a single session with a single user agent, and at
+  #    the end of the authentication process (i.e. when an OP replies
+  #    with either <tt>id_res</tt>. or <tt>cancel</tt> it may be
+  #    discarded.
+  #
+  # 2. State of relationships with servers, i.e. shared secrets
+  #    (associations) with servers and nonces seen on signed messages.
+  #    This information should persist from one session to the next
+  #    and should not be bound to a particular user-agent.
+  #
+  # These two types of storage are reflected in the first two
+  # arguments of Consumer's constructor, <tt>session</tt> and
+  # <tt>store</tt>.  <tt>session</tt> is a dict-like object and we
+  # hope your web framework provides you with one of these bound to
+  # the user agent.  <tt>store</tt> is an instance of Store.
+  #
+  # Since the store does hold secrets shared between your application
+  # and the OpenID provider, you should be careful about how you use
+  # it in a shared hosting environment.  If the filesystem or database
+  # permissions of your web host allow strangers to read from them, do
+  # not store your data there!  If you have no safe place to store
+  # your data, construct your consumer with nil for the store, and it
+  # will operate only in stateless mode.  Stateless mode may be
+  # slower, put more load on the OpenID provider, and trusts the
+  # provider to keep you safe from replay attacks.
+  #
+  # Several store implementation are provided, and the interface is
+  # fully documented so that custom stores can be used as well.  See
+  # the documentation for the Consumer class for more information on
+  # the interface for stores.  The implementations that are provided
+  # allow the consumer site to store the necessary data in several
+  # different ways, including several SQL databases and normal files
+  # on disk.
+  #
+  # = IMMEDIATE MODE
+  #
+  # In the flow described above, the user may need to confirm to the
+  # OpenID provider that it's ok to disclose his or her identity.  The
+  # provider 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.
+  #
+  # = USING THIS LIBRARY
+  #
+  # Integrating this library into an application is usually a
+  # relatively straightforward process.  The process should basically
+  # follow 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 your site which includes that OpenID URL.
+  #
+  # First, the application should instantiate a Consumer with a
+  # session for per-user state and store for shared state using the
+  # store of choice.
+  #
+  # Next, the application should call the <tt>begin</tt> method of
+  # Consumer instance.  This method takes the OpenID URL as entered by
+  # the user.  The <tt>begin</tt> method returns a CheckIDRequest
+  # object.
+  #
+  # Next, the application should call the redirect_url method on the
+  # CheckIDRequest object.  The parameter <tt>return_to</tt> is the
+  # URL that the OpenID server will send the user back to after
+  # attempting to verify his or her identity.  The <tt>realm</tt>
+  # parameter is the URL (or URL pattern) that identifies your web
+  # site to the user when he or she is authorizing it.  Send a
+  # redirect to the resulting URL to the user's browser.
+  #
+  # That's the first half of the authentication process.  The second
+  # half of the process is done after the user's OpenID Provider sends
+  # the user's browser a redirect back to your site to complete their
+  # login.
+  #
+  # When that happens, the user will contact your site at the URL
+  # given as the <tt>return_to</tt> URL to the redirect_url call made
+  # above.  The request will have several query parameters added to
+  # the URL by the OpenID provider as the information necessary to
+  # finish the request.
+  #
+  # Get a Consumer instance with the same session and store as before
+  # and call its complete() method, passing in all the received query
+  # arguments and URL currently being handled.
+  #
+  # There are multiple possible return types possible from that
+  # method. These indicate the whether or not the login was
+  # successful, and include any additional information appropriate for
+  # their type.
+  class Consumer
+    attr_accessor :session_key_prefix
+
+    # Initialize a Consumer instance.
+    #
+    # You should create a new instance of the Consumer object with
+    # every HTTP request that handles OpenID transactions.
+    #
+    # session: the session object to use to store request information.
+    # The session should behave like a hash.
+    #
+    # store: an object that implements the interface in Store.
+    def initialize(session, store)
+      @session = session
+      @store = store
+      @session_key_prefix = 'OpenID::Consumer::'
+    end
+
+    # Start the OpenID authentication process. See steps 1-2 in the
+    # overview for the Consumer class.
+    #
+    # user_url: Identity URL given by the user. This method 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.
+    #
+    # anonymous: A boolean value.  Whether to make an anonymous
+    # request of the OpenID provider.  Such a request does not ask for
+    # an authorization assertion for an OpenID identifier, but may be
+    # used with extensions to pass other data.  e.g. "I don't care who
+    # you are, but I'd like to know your time zone."
+    #
+    # Returns a CheckIDRequest object containing the discovered
+    # information, with a method for building a redirect URL to the
+    # server, as described in step 3 of the overview. This object may
+    # also be used to add extension arguments to the request, using
+    # its add_extension_arg method.
+    #
+    # Raises DiscoveryFailure when no OpenID server can be found for
+    # this URL.
+    def begin(openid_identifier, anonymous=false)
+      manager = discovery_manager(openid_identifier)
+      service = manager.get_next_service(&method(:discover))
+
+      if service.nil?
+        raise DiscoveryFailure.new("No usable OpenID services were found "\
+                                   "for #{openid_identifier.inspect}", nil)
+      else
+        begin_without_discovery(service, anonymous)
+      end
+    end
+
+    # Start OpenID verification 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.
+    #
+    # service: an OpenID service endpoint descriptor.  This object and
+    # factories for it are found in the openid/consumer/discovery.rb
+    # module.
+    #
+    # Returns an OpenID authentication request object.
+    def begin_without_discovery(service, anonymous)
+      assoc = association_manager(service).get_association
+      checkid_request = CheckIDRequest.new(assoc, service)
+      checkid_request.anonymous = anonymous
+
+      if service.compatibility_mode
+        rt_args = checkid_request.return_to_args
+        rt_args[Consumer.openid1_return_to_nonce_name] = Nonce.mk_nonce
+        rt_args[Consumer.openid1_return_to_claimed_id_name] =
+          service.claimed_id
+      end
+
+      self.last_requested_endpoint = service
+      return checkid_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.
+    #
+    # query: A hash of the query parameters for this HTTP request.
+    # Note that in rails, this is <b>not</b> <tt>params</tt> but
+    # <tt>params.reject{|k,v|request.path_parameters[k]}</tt>
+    # because <tt>controller</tt> and <tt>action</tt> and other
+    # "path parameters" are included in params.
+    #
+    # current_url: Extract the URL of the current request from your
+    # application's web request framework and specify it here to have it
+    # checked against the openid.return_to value in the response.  Do not
+    # just pass <tt>args['openid.return_to']</tt> here; that will defeat the
+    # purpose of this check.  (See OpenID Authentication 2.0 section 11.1.)
+    #
+    # If the return_to URL check fails, the status of the completion will be
+    # FAILURE.
+
+    #
+    # Returns a subclass of Response. The type of response is
+    # indicated by the status attribute, which will be one of
+    # SUCCESS, CANCEL, FAILURE, or SETUP_NEEDED.
+    def complete(query, current_url)
+      message = Message.from_post_args(query)
+      mode = message.get_arg(OPENID_NS, 'mode', 'invalid')
+      begin
+        meth = method('complete_' + mode)
+      rescue NameError
+        meth = method(:complete_invalid)
+      end
+      response = meth.call(message, current_url)
+      cleanup_last_requested_endpoint
+      if [SUCCESS, CANCEL].member?(response.status)
+        cleanup_session
+      end
+      return response
+    end
+
+    protected
+
+    def session_get(name)
+      @session[session_key(name)]
+    end
+
+    def session_set(name, val)
+      @session[session_key(name)] = val
+    end
+
+    def session_key(suffix)
+      @session_key_prefix + suffix
+    end
+
+    def last_requested_endpoint
+      session_get('last_requested_endpoint')
+    end
+
+    def last_requested_endpoint=(endpoint)
+      session_set('last_requested_endpoint', endpoint)
+    end
+
+    def cleanup_last_requested_endpoint
+      @session[session_key('last_requested_endpoint')] = nil
+    end
+
+    def discovery_manager(openid_identifier)
+      DiscoveryManager.new(@session, openid_identifier, @session_key_prefix)
+    end
+
+    def cleanup_session
+      discovery_manager(nil).cleanup(true)
+    end
+
+
+    def discover(identifier)
+      OpenID.discover(identifier)
+    end
+
+    def negotiator
+      DefaultNegotiator
+    end
+
+    def association_manager(service)
+      AssociationManager.new(@store, service.server_url,
+                             service.compatibility_mode, negotiator)
+    end
+
+    def handle_idres(message, current_url)
+      IdResHandler.new(message, current_url, @store, last_requested_endpoint)
+    end
+
+    def complete_invalid(message, unused_return_to)
+      mode = message.get_arg(OPENID_NS, 'mode', '<No mode set>')
+      return FailureResponse.new(last_requested_endpoint,
+                                 "Invalid openid.mode: #{mode}")
+    end
+
+    def complete_cancel(unused_message, unused_return_to)
+      return CancelResponse.new(last_requested_endpoint)
+    end
+
+    def complete_error(message, unused_return_to)
+      error = message.get_arg(OPENID_NS, 'error')
+      contact = message.get_arg(OPENID_NS, 'contact')
+      reference = message.get_arg(OPENID_NS, 'reference')
+
+      return FailureResponse.new(last_requested_endpoint,
+                                 error, contact, reference)
+    end
+
+    def complete_setup_needed(message, unused_return_to)
+      if message.is_openid1
+        return complete_invalid(message, nil)
+      else
+        setup_url = message.get_arg(OPENID2_NS, 'user_setup_url')
+        return SetupNeededResponse.new(last_requested_endpoint, setup_url)
+      end
+    end
+
+    def complete_id_res(message, current_url)
+      if message.is_openid1
+        setup_url = message.get_arg(OPENID1_NS, 'user_setup_url')
+        if !setup_url.nil?
+          return SetupNeededResponse.new(last_requested_endpoint, setup_url)
+        end
+      end
+
+      begin
+        idres = handle_idres(message, current_url)
+      rescue OpenIDError => why
+        return FailureResponse.new(last_requested_endpoint, why.message)
+      else
+        return SuccessResponse.new(idres.endpoint, message,
+                                     idres.signed_fields)
+      end
+    end
+  end
+end
diff --git a/vendor/gems/ruby-openid-2.1.4/lib/openid/consumer/associationmanager.rb b/vendor/gems/ruby-openid-2.1.4/lib/openid/consumer/associationmanager.rb
new file mode 100644 (file)
index 0000000..51c0f3d
--- /dev/null
@@ -0,0 +1,340 @@
+require "openid/dh"
+require "openid/util"
+require "openid/kvpost"
+require "openid/cryptutil"
+require "openid/protocolerror"
+require "openid/association"
+
+module OpenID
+  class Consumer
+
+    # A superclass for implementing Diffie-Hellman association sessions.
+    class DiffieHellmanSession
+      class << self
+        attr_reader :session_type, :secret_size, :allowed_assoc_types,
+          :hashfunc
+      end
+
+      def initialize(dh=nil)
+        if dh.nil?
+          dh = DiffieHellman.from_defaults
+        end
+        @dh = dh
+      end
+
+      # Return the query parameters for requesting an association
+      # using this Diffie-Hellman association session
+      def get_request
+        args = {'dh_consumer_public' => CryptUtil.num_to_base64(@dh.public)}
+        if (!@dh.using_default_values?)
+          args['dh_modulus'] = CryptUtil.num_to_base64(@dh.modulus)
+          args['dh_gen'] = CryptUtil.num_to_base64(@dh.generator)
+        end
+
+        return args
+      end
+
+      # Process the response from a successful association request and
+      # return the shared secret for this association
+      def extract_secret(response)
+        dh_server_public64 = response.get_arg(OPENID_NS, 'dh_server_public',
+                                              NO_DEFAULT)
+        enc_mac_key64 = response.get_arg(OPENID_NS, 'enc_mac_key', NO_DEFAULT)
+        dh_server_public = CryptUtil.base64_to_num(dh_server_public64)
+        enc_mac_key = Util.from_base64(enc_mac_key64)
+        return @dh.xor_secret(self.class.hashfunc,
+                              dh_server_public, enc_mac_key)
+      end
+    end
+
+    # A Diffie-Hellman association session that uses SHA1 as its hash
+    # function
+    class DiffieHellmanSHA1Session < DiffieHellmanSession
+      @session_type = 'DH-SHA1'
+      @secret_size = 20
+      @allowed_assoc_types = ['HMAC-SHA1']
+      @hashfunc = CryptUtil.method(:sha1)
+    end
+
+    # A Diffie-Hellman association session that uses SHA256 as its hash
+    # function
+    class DiffieHellmanSHA256Session < DiffieHellmanSession
+      @session_type = 'DH-SHA256'
+      @secret_size = 32
+      @allowed_assoc_types = ['HMAC-SHA256']
+      @hashfunc = CryptUtil.method(:sha256)
+    end
+
+    # An association session that does not use encryption
+    class NoEncryptionSession
+      class << self
+        attr_reader :session_type, :allowed_assoc_types
+      end
+      @session_type = 'no-encryption'
+      @allowed_assoc_types = ['HMAC-SHA1', 'HMAC-SHA256']
+
+      def get_request
+        return {}
+      end
+
+      def extract_secret(response)
+        mac_key64 = response.get_arg(OPENID_NS, 'mac_key', NO_DEFAULT)
+        return Util.from_base64(mac_key64)
+      end
+    end
+
+    # An object that manages creating and storing associations for an
+    # OpenID provider endpoint
+    class AssociationManager
+      def self.create_session(session_type)
+        case session_type
+        when 'no-encryption'
+          NoEncryptionSession.new
+        when 'DH-SHA1'
+          DiffieHellmanSHA1Session.new
+        when 'DH-SHA256'
+          DiffieHellmanSHA256Session.new
+        else
+          raise ArgumentError, "Unknown association session type: "\
+                               "#{session_type.inspect}"
+        end
+      end
+
+      def initialize(store, server_url, compatibility_mode=false,
+                     negotiator=nil)
+        @store = store
+        @server_url = server_url
+        @compatibility_mode = compatibility_mode
+        @negotiator = negotiator || DefaultNegotiator
+      end
+
+      def get_association
+        if @store.nil?
+          return nil
+        end
+
+        assoc = @store.get_association(@server_url)
+        if assoc.nil? || assoc.expires_in <= 0
+          assoc = negotiate_association
+          if !assoc.nil?
+            @store.store_association(@server_url, assoc)
+          end
+        end
+
+        return assoc
+      end
+
+      def negotiate_association
+        assoc_type, session_type = @negotiator.get_allowed_type
+        begin
+          return request_association(assoc_type, session_type)
+        rescue ServerError => why
+          supported_types = extract_supported_association_type(why, assoc_type)
+          if !supported_types.nil?
+            # Attempt to create an association from the assoc_type and
+            # session_type that the server told us it supported.
+            assoc_type, session_type = supported_types
+            begin
+              return request_association(assoc_type, session_type)
+            rescue ServerError => why
+              Util.log("Server #{@server_url} refused its suggested " \
+                       "association type: session_type=#{session_type}, " \
+                       "assoc_type=#{assoc_type}")
+              return nil
+            end
+          end
+        end
+      end
+
+      protected
+      def extract_supported_association_type(server_error, assoc_type)
+        # Any error message whose code is not 'unsupported-type' should
+        # be considered a total failure.
+        if (server_error.error_code != 'unsupported-type' or
+            server_error.message.is_openid1)
+          Util.log("Server error when requesting an association from "\
+                   "#{@server_url}: #{server_error.error_text}")
+          return nil
+        end
+
+        # The server didn't like the association/session type that we
+        # sent, and it sent us back a message that might tell us how to
+        # handle it.
+        Util.log("Unsupported association type #{assoc_type}: "\
+                 "#{server_error.error_text}")
+
+        # Extract the session_type and assoc_type from the error message
+        assoc_type = server_error.message.get_arg(OPENID_NS, 'assoc_type')
+        session_type = server_error.message.get_arg(OPENID_NS, 'session_type')
+
+        if assoc_type.nil? or session_type.nil?
+          Util.log("Server #{@server_url} responded with unsupported "\
+                   "association session but did not supply a fallback.")
+          return nil
+        elsif !@negotiator.allowed?(assoc_type, session_type)
+          Util.log("Server sent unsupported session/association type: "\
+                   "session_type=#{session_type}, assoc_type=#{assoc_type}")
+          return nil
+        else
+          return [assoc_type, session_type]
+        end
+      end
+
+      # Make and process one association request to this endpoint's OP
+      # endpoint URL. Returns an association object or nil if the
+      # association processing failed. Raises ServerError when the
+      # remote OpenID server returns an error.
+      def request_association(assoc_type, session_type)
+        assoc_session, args = create_associate_request(assoc_type, session_type)
+
+        begin
+          response = OpenID.make_kv_post(args, @server_url)
+          return extract_association(response, assoc_session)
+        rescue HTTPStatusError => why
+          Util.log("Got HTTP status error when requesting association: #{why}")
+          return nil
+        rescue Message::KeyNotFound => why
+          Util.log("Missing required parameter in response from "\
+                   "#{@server_url}: #{why}")
+          return nil
+
+        rescue ProtocolError => why
+          Util.log("Protocol error processing response from #{@server_url}: "\
+                   "#{why}")
+          return nil
+        end
+      end
+
+      # Create an association request for the given assoc_type and
+      # session_type. Returns a pair of the association session object
+      # and the request message that will be sent to the server.
+      def create_associate_request(assoc_type, session_type)
+        assoc_session = self.class.create_session(session_type)
+        args = {
+          'mode' => 'associate',
+          'assoc_type' => assoc_type,
+        }
+
+        if !@compatibility_mode
+          args['ns'] = OPENID2_NS
+        end
+
+        # Leave out the session type if we're in compatibility mode
+        # *and* it's no-encryption.
+        if !@compatibility_mode ||
+            assoc_session.class.session_type != 'no-encryption'
+          args['session_type'] = assoc_session.class.session_type
+        end
+
+        args.merge!(assoc_session.get_request)
+        message = Message.from_openid_args(args)
+        return assoc_session, message
+      end
+
+      # Given an association response message, extract the OpenID 1.X
+      # session type. Returns the association type for this message
+      #
+      # This function mostly takes care of the 'no-encryption' default
+      # behavior in OpenID 1.
+      #
+      # If the association type is plain-text, this function will
+      # return 'no-encryption'
+      def get_openid1_session_type(assoc_response)
+        # If it's an OpenID 1 message, allow session_type to default
+        # to nil (which signifies "no-encryption")
+        session_type = assoc_response.get_arg(OPENID1_NS, 'session_type')
+
+        # Handle the differences between no-encryption association
+        # respones in OpenID 1 and 2:
+
+        # no-encryption is not really a valid session type for
+        # OpenID 1, but we'll accept it anyway, while issuing a
+        # warning.
+        if session_type == 'no-encryption'
+          Util.log("WARNING: #{@server_url} sent 'no-encryption'"\
+                   "for OpenID 1.X")
+
+        # Missing or empty session type is the way to flag a
+        # 'no-encryption' response. Change the session type to
+        # 'no-encryption' so that it can be handled in the same
+        # way as OpenID 2 'no-encryption' respones.
+        elsif session_type == '' || session_type.nil?
+          session_type = 'no-encryption'
+        end
+
+        return session_type
+      end
+
+      def self.extract_expires_in(message)
+        # expires_in should be a base-10 string.
+        expires_in_str = message.get_arg(OPENID_NS, 'expires_in', NO_DEFAULT)
+        if !(/\A\d+\Z/ =~ expires_in_str)
+          raise ProtocolError, "Invalid expires_in field: #{expires_in_str}"
+        end
+        expires_in_str.to_i
+      end
+
+      # Attempt to extract an association from the response, given the
+      # association response message and the established association
+      # session.
+      def extract_association(assoc_response, assoc_session)
+        # Extract the common fields from the response, raising an
+        # exception if they are not found
+        assoc_type = assoc_response.get_arg(OPENID_NS, 'assoc_type',
+                                            NO_DEFAULT)
+        assoc_handle = assoc_response.get_arg(OPENID_NS, 'assoc_handle',
+                                              NO_DEFAULT)
+        expires_in = self.class.extract_expires_in(assoc_response)
+
+        # OpenID 1 has funny association session behaviour.
+        if assoc_response.is_openid1
+            session_type = get_openid1_session_type(assoc_response)
+        else
+          session_type = assoc_response.get_arg(OPENID2_NS, 'session_type',
+                                                NO_DEFAULT)
+        end
+
+        # Session type mismatch
+        if assoc_session.class.session_type != session_type
+          if (assoc_response.is_openid1 and session_type == 'no-encryption')
+            # In OpenID 1, any association request can result in a
+            # 'no-encryption' association response. Setting
+            # assoc_session to a new no-encryption session should
+            # make the rest of this function work properly for
+            # that case.
+            assoc_session = NoEncryptionSession.new
+          else
+            # Any other mismatch, regardless of protocol version
+            # results in the failure of the association session
+            # altogether.
+            raise ProtocolError, "Session type mismatch. Expected "\
+                                 "#{assoc_session.class.session_type}, got "\
+                                 "#{session_type}"
+          end
+        end
+
+        # Make sure assoc_type is valid for session_type
+        if !assoc_session.class.allowed_assoc_types.member?(assoc_type)
+          raise ProtocolError, "Unsupported assoc_type for session "\
+                               "#{assoc_session.class.session_type} "\
+                               "returned: #{assoc_type}"
+        end
+
+        # Delegate to the association session to extract the secret
+        # from the response, however is appropriate for that session
+        # type.
+        begin
+          secret = assoc_session.extract_secret(assoc_response)
+        rescue Message::KeyNotFound, ArgumentError => why
+          raise ProtocolError, "Malformed response for "\
+                               "#{assoc_session.class.session_type} "\
+                               "session: #{why.message}"
+        end
+
+
+        return Association.from_expires_in(expires_in, assoc_handle, secret,
+                                           assoc_type)
+      end
+    end
+  end
+end
diff --git a/vendor/gems/ruby-openid-2.1.4/lib/openid/consumer/checkid_request.rb b/vendor/gems/ruby-openid-2.1.4/lib/openid/consumer/checkid_request.rb
new file mode 100644 (file)
index 0000000..eb5d397
--- /dev/null
@@ -0,0 +1,186 @@
+require "openid/message"
+require "openid/util"
+
+module OpenID
+  class Consumer
+    # An object that holds the state necessary for generating an
+    # OpenID authentication request. This object holds the association
+    # with the server and the discovered information with which the
+    # request will be made.
+    #
+    # It is separate from the consumer because you may wish to add
+    # things to the request before sending it on its way to the
+    # server. It also has serialization options that let you encode
+    # the authentication request as a URL or as a form POST.
+    class CheckIDRequest
+      attr_accessor :return_to_args, :message
+      attr_reader :endpoint
+
+      # Users of this library should not create instances of this
+      # class.  Instances of this class are created by the library
+      # when needed.
+      def initialize(assoc, endpoint)
+        @assoc = assoc
+        @endpoint = endpoint
+        @return_to_args = {}
+        @message = Message.new(endpoint.preferred_namespace)
+        @anonymous = false
+      end
+
+      attr_reader :anonymous
+
+      # Set whether this request should be made anonymously. If a
+      # request is anonymous, the identifier will not be sent in the
+      # request. This is only useful if you are making another kind of
+      # request with an extension in this request.
+      #
+      # Anonymous requests are not allowed when the request is made
+      # with OpenID 1.
+      def anonymous=(is_anonymous)
+        if is_anonymous && @message.is_openid1
+          raise ArgumentError, ("OpenID1 requests MUST include the "\
+                                "identifier in the request")
+        end
+        @anonymous = is_anonymous
+      end
+
+      # Add an object that implements the extension interface for
+      # adding arguments to an OpenID message to this checkid request.
+      #
+      # extension_request: an OpenID::Extension object.
+      def add_extension(extension_request)
+        extension_request.to_message(@message)
+      end
+
+      # Add an extension argument to this OpenID authentication
+      # request. You probably want to use add_extension and the
+      # OpenID::Extension interface.
+      #
+      # Use caution when adding arguments, because they will be
+      # URL-escaped and appended to the redirect URL, which can easily
+      # get quite long.
+      def add_extension_arg(namespace, key, value)
+        @message.set_arg(namespace, key, value)
+      end
+
+      # Produce a OpenID::Message representing this request.
+      #
+      # Not specifying a return_to URL means that the user will not be
+      # returned to the site issuing the request upon its completion.
+      #
+      # If immediate mode is requested, the OpenID provider is to send
+      # back a response immediately, useful for behind-the-scenes
+      # authentication attempts.  Otherwise the OpenID provider may
+      # engage the user before providing a response.  This is the
+      # default case, as the user may need to provide credentials or
+      # approve the request before a positive response can be sent.
+      def get_message(realm, return_to=nil, immediate=false)
+        if !return_to.nil?
+          return_to = Util.append_args(return_to, @return_to_args)
+        elsif immediate
+          raise ArgumentError, ('"return_to" is mandatory when using '\
+                                '"checkid_immediate"')
+        elsif @message.is_openid1
+          raise ArgumentError, ('"return_to" is mandatory for OpenID 1 '\
+                                'requests')
+        elsif @return_to_args.empty?
+          raise ArgumentError, ('extra "return_to" arguments were specified, '\
+                                'but no return_to was specified')
+        end
+
+
+        message = @message.copy
+
+        mode = immediate ? 'checkid_immediate' : 'checkid_setup'
+        message.set_arg(OPENID_NS, 'mode', mode)
+
+        realm_key = message.is_openid1 ? 'trust_root' : 'realm'
+        message.set_arg(OPENID_NS, realm_key, realm)
+
+        if !return_to.nil?
+          message.set_arg(OPENID_NS, 'return_to', return_to)
+        end
+
+        if not @anonymous
+          if @endpoint.is_op_identifier
+            # This will never happen when we're in OpenID 1
+            # compatibility mode, as long as is_op_identifier()
+            # returns false whenever preferred_namespace returns
+            # OPENID1_NS.
+            claimed_id = request_identity = IDENTIFIER_SELECT
+          else
+            request_identity = @endpoint.get_local_id
+            claimed_id = @endpoint.claimed_id
+          end
+
+          # This is true for both OpenID 1 and 2
+          message.set_arg(OPENID_NS, 'identity', request_identity)
+
+          if message.is_openid2
+            message.set_arg(OPENID2_NS, 'claimed_id', claimed_id)
+          end
+        end
+
+        if @assoc
+          message.set_arg(OPENID_NS, 'assoc_handle', @assoc.handle)
+          assoc_log_msg = "with assocication #{@assoc.handle}"
+        else
+          assoc_log_msg = 'using stateless mode.'
+        end
+
+        Util.log("Generated #{mode} request to #{@endpoint.server_url} "\
+                 "#{assoc_log_msg}")
+        return message
+      end
+
+      # Returns a URL with an encoded OpenID request.
+      #
+      # The resulting URL is the OpenID provider's endpoint URL with
+      # parameters appended as query arguments.  You should redirect
+      # the user agent to this URL.
+      #
+      # OpenID 2.0 endpoints also accept POST requests, see
+      # 'send_redirect?' and 'form_markup'.
+      def redirect_url(realm, return_to=nil, immediate=false)
+        message = get_message(realm, return_to, immediate)
+        return message.to_url(@endpoint.server_url)
+      end
+
+      # Get html for a form to submit this request to the IDP.
+      #
+      # form_tag_attrs is a hash of attributes to be added to the form
+      # tag. 'accept-charset' and 'enctype' have defaults that can be
+      # overridden. If a value is supplied for 'action' or 'method',
+      # it will be replaced.
+      def form_markup(realm, return_to=nil, immediate=false,
+                      form_tag_attrs=nil)
+        message = get_message(realm, return_to, immediate)
+        return message.to_form_markup(@endpoint.server_url, form_tag_attrs)
+      end
+
+      # Get a complete HTML document that autosubmits the request to the IDP
+      # with javascript.  This method wraps form_markup - see that method's
+      # documentation for help with the parameters.
+      def html_markup(realm, return_to=nil, immediate=false,
+                      form_tag_attrs=nil)
+        Util.auto_submit_html(form_markup(realm, 
+                                          return_to, 
+                                          immediate, 
+                                          form_tag_attrs))
+      end
+
+      # Should this OpenID authentication request be sent as a HTTP
+      # redirect or as a POST (form submission)?
+      #
+      # This takes the same parameters as redirect_url or form_markup
+      def send_redirect?(realm, return_to=nil, immediate=false)
+        if @endpoint.compatibility_mode
+          return true
+        else
+          url = redirect_url(realm, return_to, immediate)
+          return url.length <= OPENID1_URL_LIMIT
+        end
+      end
+    end
+  end
+end
diff --git a/vendor/gems/ruby-openid-2.1.4/lib/openid/consumer/discovery.rb b/vendor/gems/ruby-openid-2.1.4/lib/openid/consumer/discovery.rb
new file mode 100644 (file)
index 0000000..01fe036
--- /dev/null
@@ -0,0 +1,498 @@
+# Functions to discover OpenID endpoints from identifiers.
+
+require 'uri'
+require 'openid/util'
+require 'openid/fetchers'
+require 'openid/urinorm'
+require 'openid/message'
+require 'openid/yadis/discovery'
+require 'openid/yadis/xrds'
+require 'openid/yadis/xri'
+require 'openid/yadis/services'
+require 'openid/yadis/filters'
+require 'openid/consumer/html_parse'
+require 'openid/yadis/xrires'
+
+module OpenID
+
+  OPENID_1_0_NS = 'http://openid.net/xmlns/1.0'
+  OPENID_IDP_2_0_TYPE = 'http://specs.openid.net/auth/2.0/server'
+  OPENID_2_0_TYPE = 'http://specs.openid.net/auth/2.0/signon'
+  OPENID_1_1_TYPE = 'http://openid.net/signon/1.1'
+  OPENID_1_0_TYPE = 'http://openid.net/signon/1.0'
+
+  OPENID_1_0_MESSAGE_NS = OPENID1_NS
+  OPENID_2_0_MESSAGE_NS = OPENID2_NS
+
+  # Object representing an OpenID service endpoint.
+  class OpenIDServiceEndpoint
+
+    # OpenID service type URIs, listed in order of preference.  The
+    # ordering of this list affects yadis and XRI service discovery.
+    OPENID_TYPE_URIS = [
+        OPENID_IDP_2_0_TYPE,
+
+        OPENID_2_0_TYPE,
+        OPENID_1_1_TYPE,
+        OPENID_1_0_TYPE,
+        ]
+
+    # the verified identifier.
+    attr_accessor :claimed_id
+
+    # For XRI, the persistent identifier.
+    attr_accessor :canonical_id
+
+    attr_accessor :server_url, :type_uris, :local_id, :used_yadis
+
+    def initialize
+      @claimed_id = nil
+      @server_url = nil
+      @type_uris = []
+      @local_id = nil
+      @canonical_id = nil
+      @used_yadis = false # whether this came from an XRDS
+      @display_identifier = nil
+    end
+
+    def display_identifier
+      return @display_identifier if @display_identifier
+
+      return @claimed_id if @claimed_id.nil? 
+
+      begin
+        parsed_identifier = URI.parse(@claimed_id)
+      rescue URI::InvalidURIError
+        raise ProtocolError, "Claimed identifier #{claimed_id} is not a valid URI"
+      end
+
+      return @claimed_id if not parsed_identifier.fragment
+
+      disp = parsed_identifier
+      disp.fragment = nil
+
+      return disp.to_s
+    end
+
+    def display_identifier=(display_identifier)
+      @display_identifier = display_identifier
+    end
+
+    def uses_extension(extension_uri)
+      return @type_uris.member?(extension_uri)
+    end
+
+    def preferred_namespace
+      if (@type_uris.member?(OPENID_IDP_2_0_TYPE) or
+          @type_uris.member?(OPENID_2_0_TYPE))
+        return OPENID_2_0_MESSAGE_NS
+      else
+        return OPENID_1_0_MESSAGE_NS
+      end
+    end
+
+    def supports_type(type_uri)
+      # Does this endpoint support this type?
+      #
+      # I consider C{/server} endpoints to implicitly support C{/signon}.
+      (
+       @type_uris.member?(type_uri) or
+       (type_uri == OPENID_2_0_TYPE and is_op_identifier())
+       )
+    end
+
+    def compatibility_mode
+      return preferred_namespace() != OPENID_2_0_MESSAGE_NS
+    end
+
+    def is_op_identifier
+      return @type_uris.member?(OPENID_IDP_2_0_TYPE)
+    end
+
+    def parse_service(yadis_url, uri, type_uris, service_element)
+      # Set the state of this object based on the contents of the
+      # service element.
+      @type_uris = type_uris
+      @server_url = uri
+      @used_yadis = true
+
+      if !is_op_identifier()
+        # XXX: This has crappy implications for Service elements that
+        # contain both 'server' and 'signon' Types.  But that's a
+        # pathological configuration anyway, so I don't think I care.
+        @local_id = OpenID.find_op_local_identifier(service_element,
+                                                    @type_uris)
+        @claimed_id = yadis_url
+      end
+    end
+
+    def get_local_id
+      # Return the identifier that should be sent as the
+      # openid.identity parameter to the server.
+      if @local_id.nil? and @canonical_id.nil?
+        return @claimed_id
+      else
+        return (@local_id or @canonical_id)
+      end
+    end
+
+    def self.from_basic_service_endpoint(endpoint)
+      # Create a new instance of this class from the endpoint object
+      # passed in.
+      #
+      # @return: nil or OpenIDServiceEndpoint for this endpoint object"""
+
+      type_uris = endpoint.match_types(OPENID_TYPE_URIS)
+
+      # If any Type URIs match and there is an endpoint URI specified,
+      # then this is an OpenID endpoint
+      if (!type_uris.nil? and !type_uris.empty?) and !endpoint.uri.nil?
+        openid_endpoint = self.new
+        openid_endpoint.parse_service(
+                                      endpoint.yadis_url,
+                                      endpoint.uri,
+                                      endpoint.type_uris,
+                                      endpoint.service_element)
+      else
+        openid_endpoint = nil
+      end
+
+      return openid_endpoint
+    end
+
+    def self.from_html(uri, html)
+      # Parse the given document as HTML looking for an OpenID <link
+      # rel=...>
+      #
+      # @rtype: [OpenIDServiceEndpoint]
+
+      discovery_types = [
+                         [OPENID_2_0_TYPE, 'openid2.provider', 'openid2.local_id'],
+                         [OPENID_1_1_TYPE, 'openid.server', 'openid.delegate'],
+                        ]
+
+      link_attrs = OpenID.parse_link_attrs(html)
+      services = []
+      discovery_types.each { |type_uri, op_endpoint_rel, local_id_rel|
+
+        op_endpoint_url = OpenID.find_first_href(link_attrs, op_endpoint_rel)
+
+        if !op_endpoint_url
+          next
+        end
+
+        service = self.new
+        service.claimed_id = uri
+        service.local_id = OpenID.find_first_href(link_attrs, local_id_rel)
+        service.server_url = op_endpoint_url
+        service.type_uris = [type_uri]
+
+        services << service
+      }
+
+      return services
+    end
+
+    def self.from_xrds(uri, xrds)
+      # Parse the given document as XRDS looking for OpenID services.
+      #
+      # @rtype: [OpenIDServiceEndpoint]
+      #
+      # @raises L{XRDSError}: When the XRDS does not parse.
+      return Yadis::apply_filter(uri, xrds, self)
+    end
+
+    def self.from_discovery_result(discoveryResult)
+      # Create endpoints from a DiscoveryResult.
+      #
+      # @type discoveryResult: L{DiscoveryResult}
+      #
+      # @rtype: list of L{OpenIDServiceEndpoint}
+      #
+      # @raises L{XRDSError}: When the XRDS does not parse.
+      if discoveryResult.is_xrds()
+        meth = self.method('from_xrds')
+      else
+        meth = self.method('from_html')
+      end
+
+      return meth.call(discoveryResult.normalized_uri,
+                       discoveryResult.response_text)
+    end
+
+    def self.from_op_endpoint_url(op_endpoint_url)
+      # Construct an OP-Identifier OpenIDServiceEndpoint object for
+      # a given OP Endpoint URL
+      #
+      # @param op_endpoint_url: The URL of the endpoint
+      # @rtype: OpenIDServiceEndpoint
+      service = self.new
+      service.server_url = op_endpoint_url
+      service.type_uris = [OPENID_IDP_2_0_TYPE]
+      return service
+    end
+
+    def to_s
+      return sprintf("<%s server_url=%s claimed_id=%s " +
+                     "local_id=%s canonical_id=%s used_yadis=%s>",
+                     self.class, @server_url, @claimed_id,
+                     @local_id, @canonical_id, @used_yadis)
+    end
+  end
+
+  def self.find_op_local_identifier(service_element, type_uris)
+    # Find the OP-Local Identifier for this xrd:Service element.
+    #
+    # This considers openid:Delegate to be a synonym for xrd:LocalID
+    # if both OpenID 1.X and OpenID 2.0 types are present. If only
+    # OpenID 1.X is present, it returns the value of
+    # openid:Delegate. If only OpenID 2.0 is present, it returns the
+    # value of xrd:LocalID. If there is more than one LocalID tag and
+    # the values are different, it raises a DiscoveryFailure. This is
+    # also triggered when the xrd:LocalID and openid:Delegate tags are
+    # different.
+
+    # XXX: Test this function on its own!
+
+    # Build the list of tags that could contain the OP-Local
+    # Identifier
+    local_id_tags = []
+    if type_uris.member?(OPENID_1_1_TYPE) or
+        type_uris.member?(OPENID_1_0_TYPE)
+      # local_id_tags << Yadis::nsTag(OPENID_1_0_NS, 'openid', 'Delegate')
+      service_element.add_namespace('openid', OPENID_1_0_NS)
+      local_id_tags << "openid:Delegate"
+    end
+
+    if type_uris.member?(OPENID_2_0_TYPE)
+      # local_id_tags.append(Yadis::nsTag(XRD_NS_2_0, 'xrd', 'LocalID'))
+      service_element.add_namespace('xrd', Yadis::XRD_NS_2_0)
+      local_id_tags << "xrd:LocalID"
+    end
+
+    # Walk through all the matching tags and make sure that they all
+    # have the same value
+    local_id = nil
+    local_id_tags.each { |local_id_tag|
+      service_element.each_element(local_id_tag) { |local_id_element|
+        if local_id.nil?
+          local_id = local_id_element.text
+        elsif local_id != local_id_element.text
+          format = 'More than one %s tag found in one service element'
+          message = sprintf(format, local_id_tag)
+          raise DiscoveryFailure.new(message, nil)
+        end
+      }
+    }
+
+    return local_id
+  end
+
+  def self.normalize_xri(xri)
+    # Normalize an XRI, stripping its scheme if present
+    m = /^xri:\/\/(.*)/.match(xri)
+    xri = m[1] if m
+    return xri
+  end
+
+  def self.normalize_url(url)
+    # Normalize a URL, converting normalization failures to
+    # DiscoveryFailure
+    begin
+      normalized = URINorm.urinorm(url)
+    rescue URI::Error => why
+      raise DiscoveryFailure.new("Error normalizing #{url}: #{why.message}", nil)
+    else
+      defragged = URI::parse(normalized)
+      defragged.fragment = nil
+      return defragged.normalize.to_s
+    end
+  end
+
+  def self.best_matching_service(service, preferred_types)
+    # Return the index of the first matching type, or something higher
+    # if no type matches.
+    #
+    # This provides an ordering in which service elements that contain
+    # a type that comes earlier in the preferred types list come
+    # before service elements that come later. If a service element
+    # has more than one type, the most preferred one wins.
+    preferred_types.each_with_index { |value, index|
+      if service.type_uris.member?(value)
+        return index
+      end
+    }
+
+    return preferred_types.length
+  end
+
+  def self.arrange_by_type(service_list, preferred_types)
+    # Rearrange service_list in a new list so services are ordered by
+    # types listed in preferred_types.  Return the new list.
+
+    # Build a list with the service elements in tuples whose
+    # comparison will prefer the one with the best matching service
+    prio_services = []
+
+    service_list.each_with_index { |s, index|
+      prio_services << [best_matching_service(s, preferred_types), index, s]
+    }
+
+    prio_services.sort!
+
+    # Now that the services are sorted by priority, remove the sort
+    # keys from the list.
+    (0...prio_services.length).each { |i|
+      prio_services[i] = prio_services[i][2]
+    }
+
+    return prio_services
+  end
+
+  def self.get_op_or_user_services(openid_services)
+    # Extract OP Identifier services.  If none found, return the rest,
+    # sorted with most preferred first according to
+    # OpenIDServiceEndpoint.openid_type_uris.
+    #
+    # openid_services is a list of OpenIDServiceEndpoint objects.
+    #
+    # Returns a list of OpenIDServiceEndpoint objects.
+
+    op_services = arrange_by_type(openid_services, [OPENID_IDP_2_0_TYPE])
+
+    openid_services = arrange_by_type(openid_services,
+                                      OpenIDServiceEndpoint::OPENID_TYPE_URIS)
+
+    if !op_services.empty?
+      return op_services
+    else
+      return openid_services
+    end
+  end
+
+  def self.discover_yadis(uri)
+    # Discover OpenID services for a URI. Tries Yadis and falls back
+    # on old-style <link rel='...'> discovery if Yadis fails.
+    #
+    # @param uri: normalized identity URL
+    # @type uri: str
+    # 
+    # @return: (claimed_id, services)
+    # @rtype: (str, list(OpenIDServiceEndpoint))
+    #
+    # @raises DiscoveryFailure: when discovery fails.
+
+    # Might raise a yadis.discover.DiscoveryFailure if no document
+    # came back for that URI at all.  I don't think falling back to
+    # OpenID 1.0 discovery on the same URL will help, so don't bother
+    # to catch it.
+    response = Yadis.discover(uri)
+
+    yadis_url = response.normalized_uri
+    body = response.response_text
+
+    begin
+      openid_services = OpenIDServiceEndpoint.from_xrds(yadis_url, body)
+    rescue Yadis::XRDSError
+      # Does not parse as a Yadis XRDS file
+      openid_services = []
+    end
+
+    if openid_services.empty?
+      # Either not an XRDS or there are no OpenID services.
+
+      if response.is_xrds
+        # if we got the Yadis content-type or followed the Yadis
+        # header, re-fetch the document without following the Yadis
+        # header, with no Accept header.
+        return self.discover_no_yadis(uri)
+      end
+
+      # Try to parse the response as HTML.
+      # <link rel="...">
+      openid_services = OpenIDServiceEndpoint.from_html(yadis_url, body)
+    end
+
+    return [yadis_url, self.get_op_or_user_services(openid_services)]
+  end
+
+  def self.discover_xri(iname)
+    endpoints = []
+    iname = self.normalize_xri(iname)
+
+    begin
+      canonical_id, services = Yadis::XRI::ProxyResolver.new().query(
+            iname, OpenIDServiceEndpoint::OPENID_TYPE_URIS)
+
+      if canonical_id.nil?
+        raise Yadis::XRDSError.new(sprintf('No CanonicalID found for XRI %s', iname))
+      end
+
+      flt = Yadis.make_filter(OpenIDServiceEndpoint)
+
+      services.each { |service_element|
+        endpoints += flt.get_service_endpoints(iname, service_element)
+      }
+    rescue Yadis::XRDSError => why
+      Util.log('xrds error on ' + iname + ': ' + why.to_s)
+    end
+
+    endpoints.each { |endpoint|
+      # Is there a way to pass this through the filter to the endpoint
+      # constructor instead of tacking it on after?
+      endpoint.canonical_id = canonical_id
+      endpoint.claimed_id = canonical_id
+      endpoint.display_identifier = iname
+    }
+
+    # FIXME: returned xri should probably be in some normal form
+    return [iname, self.get_op_or_user_services(endpoints)]
+  end
+
+  def self.discover_no_yadis(uri)
+    http_resp = OpenID.fetch(uri)
+    if http_resp.code != "200" and http_resp.code != "206"
+      raise DiscoveryFailure.new(
+        "HTTP Response status from identity URL host is not \"200\". "\
+        "Got status #{http_resp.code.inspect}", http_resp)
+    end
+
+    claimed_id = http_resp.final_url
+    openid_services = OpenIDServiceEndpoint.from_html(
+        claimed_id, http_resp.body)
+    return [claimed_id, openid_services]
+  end
+
+  def self.discover_uri(uri)
+    # Hack to work around URI parsing for URls with *no* scheme.
+    if uri.index("://").nil?
+      uri = 'http://' + uri
+    end
+
+    begin
+      parsed = URI::parse(uri)
+    rescue URI::InvalidURIError => why
+      raise DiscoveryFailure.new("URI is not valid: #{why.message}", nil)
+    end
+
+    if !parsed.scheme.nil? and !parsed.scheme.empty?
+      if !['http', 'https'].member?(parsed.scheme)
+        raise DiscoveryFailure.new(
+                "URI scheme #{parsed.scheme} is not HTTP or HTTPS", nil)
+      end
+    end
+
+    uri = self.normalize_url(uri)
+    claimed_id, openid_services = self.discover_yadis(uri)
+    claimed_id = self.normalize_url(claimed_id)
+    return [claimed_id, openid_services]
+  end
+
+  def self.discover(identifier)
+    if Yadis::XRI::identifier_scheme(identifier) == :xri
+      normalized_identifier, services = discover_xri(identifier)
+    else
+      return discover_uri(identifier)
+    end
+  end
+end
diff --git a/vendor/gems/ruby-openid-2.1.4/lib/openid/consumer/discovery_manager.rb b/vendor/gems/ruby-openid-2.1.4/lib/openid/consumer/discovery_manager.rb
new file mode 100644 (file)
index 0000000..8f83811
--- /dev/null
@@ -0,0 +1,123 @@
+module OpenID
+  class Consumer
+
+    # A set of discovered services, for tracking which providers have
+    # been attempted for an OpenID identifier
+    class DiscoveredServices
+      attr_reader :current
+
+      def initialize(starting_url, yadis_url, services)
+        @starting_url = starting_url
+        @yadis_url = yadis_url
+        @services = services.dup
+        @current = nil
+      end
+
+      def next
+        @current = @services.shift
+      end
+
+      def for_url?(url)
+        [@starting_url, @yadis_url].member?(url)
+      end
+
+      def started?
+        !@current.nil?
+      end
+
+      def empty?
+        @services.empty?
+      end
+    end
+
+    # Manages calling discovery and tracking which endpoints have
+    # already been attempted.
+    class DiscoveryManager
+      def initialize(session, url, session_key_suffix=nil)
+        @url = url
+
+        @session = session
+        @session_key_suffix = session_key_suffix || 'auth'
+      end
+
+      def get_next_service
+        manager = get_manager
+        if !manager.nil? && manager.empty?
+          destroy_manager
+          manager = nil
+        end
+
+        if manager.nil?
+          yadis_url, services = yield @url
+          manager = create_manager(yadis_url, services)
+        end
+
+        if !manager.nil?
+          service = manager.next
+          store(manager)
+        else
+          service = nil
+        end
+
+        return service
+      end
+
+      def cleanup(force=false)
+        manager = get_manager(force)
+        if !manager.nil?
+          service = manager.current
+          destroy_manager(force)
+        else
+          service = nil
+        end
+        return service
+      end
+
+      protected
+
+      def get_manager(force=false)
+        manager = load
+        if force || manager.nil? || manager.for_url?(@url)
+          return manager
+        else
+          return nil
+        end
+      end
+
+      def create_manager(yadis_url, services)
+        manager = get_manager
+        if !manager.nil?
+          raise StandardError, "There is already a manager for #{yadis_url}"
+        end
+        if services.empty?
+          return nil
+        end
+        manager = DiscoveredServices.new(@url, yadis_url, services)
+        store(manager)
+        return manager
+      end
+
+      def destroy_manager(force=false)
+        if !get_manager(force).nil?
+          destroy!
+        end
+      end
+
+      def session_key
+        'OpenID::Consumer::DiscoveredServices::' + @session_key_suffix
+      end
+
+      def store(manager)
+        @session[session_key] = manager
+      end
+
+      def load
+        @session[session_key]
+      end
+
+      def destroy!
+        @session[session_key] = nil
+      end
+    end
+  end
+end
diff --git a/vendor/gems/ruby-openid-2.1.4/lib/openid/consumer/html_parse.rb b/vendor/gems/ruby-openid-2.1.4/lib/openid/consumer/html_parse.rb
new file mode 100644 (file)
index 0000000..579874c
--- /dev/null
@@ -0,0 +1,134 @@
+require "openid/yadis/htmltokenizer"
+
+module OpenID
+
+  # Stuff to remove before we start looking for tags
+  REMOVED_RE = /
+    # Comments
+    <!--.*?-->
+
+    # CDATA blocks
+  | <!\[CDATA\[.*?\]\]>
+
+    # script blocks
+  | <script\b
+
+    # make sure script is not an XML namespace
+    (?!:)
+
+    [^>]*>.*?<\/script>
+
+  /mixu 
+
+  def OpenID.openid_unescape(s)
+    s.gsub('&amp;','&').gsub('&lt;','<').gsub('&gt;','>').gsub('&quot;','"')
+  end
+
+  def OpenID.unescape_hash(h)
+    newh = {}
+    h.map{|k,v|
+      newh[k]=openid_unescape(v)
+    }
+    newh
+  end
+
+
+  def OpenID.parse_link_attrs(html)
+    stripped = html.gsub(REMOVED_RE,'')
+    parser = HTMLTokenizer.new(stripped)
+
+    links = []
+    # to keep track of whether or not we are in the head element
+    in_head = false
+    in_html = false
+    saw_head = false
+
+    begin
+      while el = parser.getTag('head', '/head', 'link', 'body', '/body', 
+                               'html', '/html')
+        
+        # we are leaving head or have reached body, so we bail
+        return links if ['/head', 'body', '/body', '/html'].member?(el.tag_name)
+
+        # enforce html > head > link
+        if el.tag_name == 'html'
+          in_html = true
+        end
+        next unless in_html
+        if el.tag_name == 'head'
+          if saw_head
+            return links #only allow one head
+          end
+          saw_head = true
+          unless el.to_s[-2] == 47 # tag ends with a /: a short tag
+            in_head = true
+          end
+        end
+        next unless in_head
+
+        return links if el.tag_name == 'html'
+
+        if el.tag_name == 'link'
+          links << unescape_hash(el.attr_hash)
+        end
+        
+      end
+    rescue Exception # just stop parsing if there's an error
+    end
+    return links
+  end
+
+  def OpenID.rel_matches(rel_attr, target_rel)
+    # Does this target_rel appear in the rel_str?
+    # XXX: TESTME
+    rels = rel_attr.strip().split()
+    rels.each { |rel|
+      rel = rel.downcase
+      if rel == target_rel
+        return true
+      end
+    }
+
+    return false
+  end
+
+  def OpenID.link_has_rel(link_attrs, target_rel)
+    # Does this link have target_rel as a relationship?
+
+    # XXX: TESTME
+    rel_attr = link_attrs['rel']
+    return (rel_attr and rel_matches(rel_attr, target_rel))
+  end
+
+  def OpenID.find_links_rel(link_attrs_list, target_rel)
+    # Filter the list of link attributes on whether it has target_rel
+    # as a relationship.
+
+    # XXX: TESTME
+    matchesTarget = lambda { |attrs| link_has_rel(attrs, target_rel) }
+    result = []
+
+    link_attrs_list.each { |item|
+      if matchesTarget.call(item)
+        result << item
+      end
+    }
+
+    return result
+  end
+
+  def OpenID.find_first_href(link_attrs_list, target_rel)
+    # Return the value of the href attribute for the first link tag in
+    # the list that has target_rel as a relationship.
+
+    # XXX: TESTME
+    matches = find_links_rel(link_attrs_list, target_rel)
+    if !matches or matches.empty?
+      return nil
+    end
+
+    first = matches[0]
+    return first['href']
+  end
+end
+
diff --git a/vendor/gems/ruby-openid-2.1.4/lib/openid/consumer/idres.rb b/vendor/gems/ruby-openid-2.1.4/lib/openid/consumer/idres.rb
new file mode 100644 (file)
index 0000000..ab924e2
--- /dev/null
@@ -0,0 +1,523 @@
+require "openid/message"
+require "openid/protocolerror"
+require "openid/kvpost"
+require "openid/consumer/discovery"
+require "openid/urinorm"
+
+module OpenID
+  class TypeURIMismatch < ProtocolError
+    attr_reader :type_uri, :endpoint
+
+    def initialize(type_uri, endpoint)
+      @type_uri = type_uri
+      @endpoint = endpoint
+    end
+  end
+
+  class Consumer
+    @openid1_return_to_nonce_name = 'rp_nonce'
+    @openid1_return_to_claimed_id_name = 'openid1_claimed_id'
+
+    # Set the name of the query parameter that this library will use
+    # to thread a nonce through an OpenID 1 transaction. It will be
+    # appended to the return_to URL.
+    def self.openid1_return_to_nonce_name=(query_arg_name)
+      @openid1_return_to_nonce_name = query_arg_name
+    end
+
+    # See openid1_return_to_nonce_name= documentation
+    def self.openid1_return_to_nonce_name
+      @openid1_return_to_nonce_name
+    end
+
+    # Set the name of the query parameter that this library will use
+    # to thread the requested URL through an OpenID 1 transaction (for
+    # use when verifying discovered information). It will be appended
+    # to the return_to URL.
+    def self.openid1_return_to_claimed_id_name=(query_arg_name)
+      @openid1_return_to_claimed_id_name = query_arg_name
+    end
+
+    # See openid1_return_to_claimed_id_name=
+    def self.openid1_return_to_claimed_id_name
+      @openid1_return_to_claimed_id_name
+    end
+
+    # Handles an openid.mode=id_res response. This object is
+    # instantiated and used by the Consumer.
+    class IdResHandler
+      attr_reader :endpoint, :message
+
+      def initialize(message, current_url, store=nil, endpoint=nil)
+        @store = store # Fer the nonce and invalidate_handle
+        @message = message
+        @endpoint = endpoint
+        @current_url = current_url
+        @signed_list = nil
+
+        # Start the verification process
+        id_res
+      end
+
+      def signed_fields
+        signed_list.map {|x| 'openid.' + x}
+      end
+
+      protected
+
+      # This method will raise ProtocolError unless the request is a
+      # valid id_res response. Once it has been verified, the methods
+      # 'endpoint', 'message', and 'signed_fields' contain the
+      # verified information.
+      def id_res
+        check_for_fields
+        verify_return_to
+        verify_discovery_results
+        check_signature
+        check_nonce
+      end
+
+      def server_url
+        @endpoint.nil? ? nil : @endpoint.server_url
+      end
+
+      def openid_namespace
+        @message.get_openid_namespace
+      end
+
+      def fetch(field, default=NO_DEFAULT)
+        @message.get_arg(OPENID_NS, field, default)
+      end
+
+      def signed_list
+        if @signed_list.nil?
+          signed_list_str = fetch('signed', nil)
+          if signed_list_str.nil?
+            raise ProtocolError, 'Response missing signed list'
+          end
+
+          @signed_list = signed_list_str.split(',', -1)
+        end
+        @signed_list
+      end
+
+      def check_for_fields
+        # XXX: if a field is missing, we should not have to explicitly
+        # check that it's present, just make sure that the fields are
+        # actually being used by the rest of the code in
+        # tests. Although, which fields are signed does need to be
+        # checked somewhere.
+        basic_fields = ['return_to', 'assoc_handle', 'sig', 'signed']
+        basic_sig_fields = ['return_to', 'identity']
+
+        case openid_namespace
+        when OPENID2_NS
+          require_fields = basic_fields + ['op_endpoint']
+          require_sigs = basic_sig_fields +
+            ['response_nonce', 'claimed_id', 'assoc_handle',]
+        when OPENID1_NS
+          require_fields = basic_fields + ['identity']
+          require_sigs = basic_sig_fields
+        else
+          raise RuntimeError, "check_for_fields doesn't know about "\
+                              "namespace #{openid_namespace.inspect}"
+        end
+
+        require_fields.each do |field|
+          if !@message.has_key?(OPENID_NS, field)
+            raise ProtocolError, "Missing required field #{field}"
+          end
+        end
+
+        require_sigs.each do |field|
+          # Field is present and not in signed list
+          if @message.has_key?(OPENID_NS, field) && !signed_list.member?(field)
+            raise ProtocolError, "#{field.inspect} not signed"
+          end
+        end
+      end
+
+      def verify_return_to
+        begin
+          msg_return_to = URI.parse(URINorm::urinorm(fetch('return_to')))
+        rescue URI::InvalidURIError
+          raise ProtocolError, ("return_to is not a valid URI")
+        end
+
+        verify_return_to_args(msg_return_to)
+        if !@current_url.nil?
+          verify_return_to_base(msg_return_to)
+        end
+      end
+
+      def verify_return_to_args(msg_return_to)
+        return_to_parsed_query = {}
+        if !msg_return_to.query.nil?
+          CGI.parse(msg_return_to.query).each_pair do |k, vs|
+            return_to_parsed_query[k] = vs[0]
+          end
+        end
+        query = @message.to_post_args
+        return_to_parsed_query.each_pair do |rt_key, rt_val|
+          msg_val = query[rt_key]
+          if msg_val.nil?
+            raise ProtocolError, "Message missing return_to argument '#{rt_key}'"
+          elsif msg_val != rt_val
+            raise ProtocolError, ("Parameter '#{rt_key}' value "\
+                                  "#{msg_val.inspect} does not match "\
+                                  "return_to's value #{rt_val.inspect}")
+          end
+        end
+        @message.get_args(BARE_NS).each_pair do |bare_key, bare_val|
+          rt_val = return_to_parsed_query[bare_key]
+          if not return_to_parsed_query.has_key? bare_key
+            # This may be caused by your web framework throwing extra
+            # entries in to your parameters hash that were not GET or
+            # POST parameters.  For example, Rails has been known to
+            # add "controller" and "action" keys; another server adds
+            # at least a "format" key.
+            raise ProtocolError, ("Unexpected parameter (not on return_to): "\
+                                  "'#{bare_key}'=#{rt_val.inspect})")
+          end
+          if rt_val != bare_val
+            raise ProtocolError, ("Parameter '#{bare_key}' value "\
+                                  "#{bare_val.inspect} does not match "\
+                                  "return_to's value #{rt_val.inspect}")
+          end
+        end
+      end
+
+      def verify_return_to_base(msg_return_to)
+        begin
+          app_parsed = URI.parse(URINorm::urinorm(@current_url))
+        rescue URI::InvalidURIError
+          raise ProtocolError, "current_url is not a valid URI: #{@current_url}"
+        end
+
+        [:scheme, :host, :port, :path].each do |meth|
+          if msg_return_to.send(meth) != app_parsed.send(meth)
+            raise ProtocolError, "return_to #{meth.to_s} does not match"
+          end
+        end
+      end
+
+      # Raises ProtocolError if the signature is bad
+      def check_signature
+        if @store.nil?
+          assoc = nil
+        else
+          assoc = @store.get_association(server_url, fetch('assoc_handle'))
+        end
+
+        if assoc.nil?
+          check_auth
+        else
+          if assoc.expires_in <= 0
+            # XXX: It might be a good idea sometimes to re-start the
+            # authentication with a new association. Doing it
+            # automatically opens the possibility for
+            # denial-of-service by a server that just returns expired
+            # associations (or really short-lived associations)
+            raise ProtocolError, "Association with #{server_url} expired"
+          elsif !assoc.check_message_signature(@message)
+            raise ProtocolError, "Bad signature in response from #{server_url}"
+          end
+        end
+      end
+
+      def check_auth
+        Util.log("Using 'check_authentication' with #{server_url}")
+        begin
+          request = create_check_auth_request
+        rescue Message::KeyNotFound => why
+          raise ProtocolError, "Could not generate 'check_authentication' "\
+                               "request: #{why.message}"
+        end
+
+        response = OpenID.make_kv_post(request, server_url)
+
+        process_check_auth_response(response)
+      end
+
+      def create_check_auth_request
+        signed_list = @message.get_arg(OPENID_NS, 'signed', NO_DEFAULT).split(',')
+
+        # check that we got all the signed arguments
+        signed_list.each {|k|
+          @message.get_aliased_arg(k, NO_DEFAULT)
+        }
+
+        ca_message = @message.copy
+        ca_message.set_arg(OPENID_NS, 'mode', 'check_authentication')
+
+        return ca_message
+      end
+
+      # Process the response message from a check_authentication
+      # request, invalidating associations if requested.
+      def process_check_auth_response(response)
+        is_valid = response.get_arg(OPENID_NS, 'is_valid', 'false')
+
+        invalidate_handle = response.get_arg(OPENID_NS, 'invalidate_handle')
+        if !invalidate_handle.nil?
+          Util.log("Received 'invalidate_handle' from server #{server_url}")
+          if @store.nil?
+            Util.log('Unexpectedly got "invalidate_handle" without a store!')
+          else
+            @store.remove_association(server_url, invalidate_handle)
+          end
+        end
+
+        if is_valid != 'true'
+          raise ProtocolError, ("Server #{server_url} responds that the "\
+                                "'check_authentication' call is not valid")
+        end
+      end
+
+      def check_nonce
+        case openid_namespace
+        when OPENID1_NS
+          nonce =
+            @message.get_arg(BARE_NS, Consumer.openid1_return_to_nonce_name)
+
+          # We generated the nonce, so it uses the empty string as the
+          # server URL
+          server_url = ''
+        when OPENID2_NS
+          nonce = @message.get_arg(OPENID2_NS, 'response_nonce')
+          server_url = self.server_url
+        else
+          raise StandardError, 'Not reached'
+        end
+
+        if nonce.nil?
+          raise ProtocolError, 'Nonce missing from response'
+        end
+
+        begin
+          time, extra = Nonce.split_nonce(nonce)
+        rescue ArgumentError => why
+          raise ProtocolError, "Malformed nonce: #{nonce.inspect}"
+        end
+
+        if !@store.nil? && !@store.use_nonce(server_url, time, extra)
+          raise ProtocolError, ("Nonce already used or out of range: "\
+                               "#{nonce.inspect}")
+        end
+      end
+
+      def verify_discovery_results
+        begin
+          case openid_namespace
+          when OPENID1_NS
+            verify_discovery_results_openid1
+          when OPENID2_NS
+            verify_discovery_results_openid2
+          else
+            raise StandardError, "Not reached: #{openid_namespace}"
+          end
+        rescue Message::KeyNotFound => why
+          raise ProtocolError, "Missing required field: #{why.message}"
+        end
+      end
+
+      def verify_discovery_results_openid2
+        to_match = OpenIDServiceEndpoint.new
+        to_match.type_uris = [OPENID_2_0_TYPE]
+        to_match.claimed_id = fetch('claimed_id', nil)
+        to_match.local_id = fetch('identity', nil)
+        to_match.server_url = fetch('op_endpoint')
+
+        if to_match.claimed_id.nil? && !to_match.local_id.nil?
+          raise ProtocolError, ('openid.identity is present without '\
+                                'openid.claimed_id')
+        elsif !to_match.claimed_id.nil? && to_match.local_id.nil?
+          raise ProtocolError, ('openid.claimed_id is present without '\
+                                'openid.identity')
+
+        # This is a response without identifiers, so there's really no
+        # checking that we can do, so return an endpoint that's for
+        # the specified `openid.op_endpoint'
+        elsif to_match.claimed_id.nil?
+          @endpoint =
+            OpenIDServiceEndpoint.from_op_endpoint_url(to_match.server_url)
+          return
+        end
+
+        if @endpoint.nil?
+          Util.log('No pre-discovered information supplied')
+          discover_and_verify(to_match.claimed_id, [to_match])
+        else
+          begin
+            verify_discovery_single(@endpoint, to_match)
+          rescue ProtocolError => why
+            Util.log("Error attempting to use stored discovery "\
+                     "information: #{why.message}")
+            Util.log("Attempting discovery to verify endpoint")
+            discover_and_verify(to_match.claimed_id, [to_match])
+          end
+        end
+
+        if @endpoint.claimed_id != to_match.claimed_id
+          @endpoint = @endpoint.dup
+          @endpoint.claimed_id = to_match.claimed_id
+        end
+      end
+
+      def verify_discovery_results_openid1
+        claimed_id =
+          @message.get_arg(BARE_NS, Consumer.openid1_return_to_claimed_id_name)
+
+        if claimed_id.nil?
+          if @endpoint.nil?
+            raise ProtocolError, ("When using OpenID 1, the claimed ID must "\
+                                  "be supplied, either by passing it through "\
+                                  "as a return_to parameter or by using a "\
+                                  "session, and supplied to the IdResHandler "\
+                                  "when it is constructed.")
+          else
+            claimed_id = @endpoint.claimed_id
+          end
+        end
+
+        to_match = OpenIDServiceEndpoint.new
+        to_match.type_uris = [OPENID_1_1_TYPE]
+        to_match.local_id = fetch('identity')
+        # Restore delegate information from the initiation phase
+        to_match.claimed_id = claimed_id
+
+        to_match_1_0 = to_match.dup
+        to_match_1_0.type_uris = [OPENID_1_0_TYPE]
+
+        if !@endpoint.nil?
+          begin
+            begin
+              verify_discovery_single(@endpoint, to_match)
+            rescue TypeURIMismatch
+              verify_discovery_single(@endpoint, to_match_1_0)
+            end
+          rescue ProtocolError => why
+            Util.log('Error attempting to use stored discovery information: ' +
+                     why.message)
+            Util.log('Attempting discovery to verify endpoint')
+          else
+            return @endpoint
+          end
+        end
+
+        # Either no endpoint was supplied or OpenID 1.x verification
+        # of the information that's in the message failed on that
+        # endpoint.
+        discover_and_verify(to_match.claimed_id, [to_match, to_match_1_0])
+      end
+
+      # Given an endpoint object created from the information in an
+      # OpenID response, perform discovery and verify the discovery
+      # results, returning the matching endpoint that is the result of
+      # doing that discovery.
+      def discover_and_verify(claimed_id, to_match_endpoints)
+        Util.log("Performing discovery on #{claimed_id}")
+        _, services = OpenID.discover(claimed_id)
+        if services.length == 0
+          # XXX: this might want to be something other than
+          # ProtocolError. In Python, it's DiscoveryFailure
+          raise ProtocolError, ("No OpenID information found at "\
+                                "#{claimed_id}")
+        end
+        verify_discovered_services(claimed_id, services, to_match_endpoints)
+      end
+
+
+      def verify_discovered_services(claimed_id, services, to_match_endpoints)
+        # Search the services resulting from discovery to find one
+        # that matches the information from the assertion
+        failure_messages = []
+        for endpoint in services
+          for to_match_endpoint in to_match_endpoints
+            begin
+              verify_discovery_single(endpoint, to_match_endpoint)
+            rescue ProtocolError => why
+              failure_messages << why.message
+            else
+              # It matches, so discover verification has
+              # succeeded. Return this endpoint.
+              @endpoint = endpoint
+              return
+            end
+          end
+        end
+
+        Util.log("Discovery verification failure for #{claimed_id}")
+        failure_messages.each do |failure_message|
+          Util.log(" * Endpoint mismatch: " + failure_message)
+        end
+
+        # XXX: is DiscoveryFailure in Python OpenID
+        raise ProtocolError, ("No matching endpoint found after "\
+                              "discovering #{claimed_id}")
+      end
+
+      def verify_discovery_single(endpoint, to_match)
+        # Every type URI that's in the to_match endpoint has to be
+        # present in the discovered endpoint.
+        for type_uri in to_match.type_uris
+          if !endpoint.uses_extension(type_uri)
+            raise TypeURIMismatch.new(type_uri, endpoint)
+          end
+        end
+
+        # Fragments do not influence discovery, so we can't compare a
+        # claimed identifier with a fragment to discovered information.
+        defragged_claimed_id =
+          case Yadis::XRI.identifier_scheme(endpoint.claimed_id)
+          when :xri
+            endpoint.claimed_id
+          when :uri
+            begin
+              parsed = URI.parse(endpoint.claimed_id)
+            rescue URI::InvalidURIError
+              endpoint.claimed_id
+            else
+              parsed.fragment = nil
+              parsed.to_s
+            end
+          else
+            raise StandardError, 'Not reached'
+          end
+
+        if defragged_claimed_id != endpoint.claimed_id
+          raise ProtocolError, ("Claimed ID does not match (different "\
+                                "subjects!), Expected "\
+                                "#{defragged_claimed_id}, got "\
+                                "#{endpoint.claimed_id}")
+        end
+
+        if to_match.get_local_id != endpoint.get_local_id
+          raise ProtocolError, ("local_id mismatch. Expected "\
+                                "#{to_match.get_local_id}, got "\
+                                "#{endpoint.get_local_id}")
+        end
+
+        # If the server URL is nil, this must be an OpenID 1
+        # response, because op_endpoint is a required parameter in
+        # OpenID 2. In that case, we don't actually care what the
+        # discovered server_url is, because signature checking or
+        # check_auth should take care of that check for us.
+        if to_match.server_url.nil?
+          if to_match.preferred_namespace != OPENID1_NS
+            raise StandardError,
+            "The code calling this must ensure that OpenID 2 "\
+            "responses have a non-none `openid.op_endpoint' and "\
+            "that it is set as the `server_url' attribute of the "\
+            "`to_match' endpoint."
+          end
+        elsif to_match.server_url != endpoint.server_url
+          raise ProtocolError, ("OP Endpoint mismatch. Expected"\
+                                "#{to_match.server_url}, got "\
+                                "#{endpoint.server_url}")
+        end
+      end
+
+    end
+  end
+end
diff --git a/vendor/gems/ruby-openid-2.1.4/lib/openid/consumer/responses.rb b/vendor/gems/ruby-openid-2.1.4/lib/openid/consumer/responses.rb
new file mode 100644 (file)
index 0000000..9126239
--- /dev/null
@@ -0,0 +1,148 @@
+module OpenID
+  class Consumer
+    # Code returned when either the of the
+    # OpenID::OpenIDConsumer.begin_auth or OpenID::OpenIDConsumer.complete_auth
+    # methods return successfully.
+    SUCCESS = :success
+
+    # Code OpenID::OpenIDConsumer.complete_auth
+    # returns when the value it received indicated an invalid login.
+    FAILURE = :failure
+
+    # Code returned by OpenIDConsumer.complete_auth when the user
+    # cancels the operation from the server.
+    CANCEL = :cancel
+
+    # Code returned by OpenID::OpenIDConsumer.complete_auth when the
+    # OpenIDConsumer instance is in immediate mode and ther server sends back a
+    # URL for the user to login with.
+    SETUP_NEEDED = :setup_needed
+
+
+    module Response
+      attr_reader :endpoint
+
+      def status
+        self.class::STATUS
+      end
+
+      # The identity URL that has been authenticated; the Claimed Identifier.
+      # See also display_identifier.
+      def identity_url
+        @endpoint ? @endpoint.claimed_id : nil
+      end
+
+      # The display identifier is related to the Claimed Identifier, but the
+      # two are not always identical.  The display identifier is something the
+      # user should recognize as what they entered, whereas the response's
+      # claimed identifier (in the identity_url attribute) may have extra
+      # information for better persistence.
+      #
+      # URLs will be stripped of their fragments for display.  XRIs will
+      # display the human-readable identifier (i-name) instead of the
+      # persistent identifier (i-number).
+      #
+      # Use the display identifier in your user interface.  Use identity_url
+      # for querying your database or authorization server, or other
+      # identifier equality comparisons.
+      def display_identifier
+        @endpoint ? @endpoint.display_identifier : nil
+      end
+    end
+
+    # A successful acknowledgement from the OpenID server that the
+    # supplied URL is, indeed controlled by the requesting agent.
+    class SuccessResponse
+      include Response
+
+      STATUS = SUCCESS
+
+      attr_reader :message, :signed_fields
+
+      def initialize(endpoint, message, signed_fields)
+        # Don't use :endpoint=, because endpoint should never be nil
+        # for a successfull transaction.
+        @endpoint = endpoint
+        @identity_url = endpoint.claimed_id
+        @message = message
+        @signed_fields = signed_fields
+      end
+
+      # Was this authentication response an OpenID 1 authentication
+      # response?
+      def is_openid1
+        @message.is_openid1
+      end
+
+      # Return whether a particular key is signed, regardless of its
+      # namespace alias
+      def signed?(ns_uri, ns_key)
+        @signed_fields.member?(@message.get_key(ns_uri, ns_key))
+      end
+
+      # Return the specified signed field if available, otherwise
+      # return default
+      def get_signed(ns_uri, ns_key, default=nil)
+        if singed?(ns_uri, ns_key)
+          return @message.get_arg(ns_uri, ns_key, default)
+        else
+          return default
+        end
+      end
+
+      # Get signed arguments from the response message.  Return a dict
+      # of all arguments in the specified namespace.  If any of the
+      # arguments are not signed, return nil.
+      def get_signed_ns(ns_uri)
+        msg_args = @message.get_args(ns_uri)
+        msg_args.each_key do |key|
+          if !signed?(ns_uri, key)
+            return nil
+          end
+        end
+        return msg_args
+      end
+
+      # Return response arguments in the specified namespace.
+      # If require_signed is true and the arguments are not signed,
+      # return nil.
+      def extension_response(namespace_uri, require_signed)
+        if require_signed
+          get_signed_ns(namespace_uri)
+        else
+          @message.get_args(namespace_uri)
+        end
+      end
+    end
+
+    class FailureResponse
+      include Response
+      STATUS = FAILURE
+
+      attr_reader :message, :contact, :reference
+      def initialize(endpoint, message, contact=nil, reference=nil)
+        @endpoint = endpoint
+        @message = message
+        @contact = contact
+        @reference = reference
+      end
+    end
+
+    class CancelResponse
+      include Response
+      STATUS = CANCEL
+      def initialize(endpoint)
+        @endpoint = endpoint
+      end
+    end
+
+    class SetupNeededResponse
+      include Response
+      STATUS = SETUP_NEEDED
+      def initialize(endpoint, setup_url)
+        @endpoint = endpoint
+        @setup_url = setup_url
+      end
+    end
+  end
+end
diff --git a/vendor/gems/ruby-openid-2.1.4/lib/openid/cryptutil.rb b/vendor/gems/ruby-openid-2.1.4/lib/openid/cryptutil.rb
new file mode 100644 (file)
index 0000000..d8ffead
--- /dev/null
@@ -0,0 +1,97 @@
+require "openid/util"
+require "digest/sha1"
+require "digest/sha2"
+begin
+  require "digest/hmac"
+rescue LoadError
+  require "hmac/sha1"
+  require "hmac/sha2"
+end
+
+module OpenID
+  # This module contains everything needed to perform low-level
+  # cryptograph and data manipulation tasks.
+  module CryptUtil
+
+    # Generate a random number, doing a little extra work to make it
+    # more likely that it's suitable for cryptography. If your system
+    # doesn't have /dev/urandom then this number is not
+    # cryptographically safe. See
+    # <http://www.cosine.org/2007/08/07/security-ruby-kernel-rand/>
+    # for more information.  max is the largest possible value of such
+    # a random number, where the result will be less than max.
+    def CryptUtil.rand(max)
+      Kernel.srand()
+      return Kernel.rand(max)
+    end
+
+    def CryptUtil.sha1(text)
+      return Digest::SHA1.digest(text)
+    end
+
+    def CryptUtil.hmac_sha1(key, text)
+      if Digest.const_defined? :HMAC      
+        Digest::HMAC.new(key,Digest::SHA1).update(text).digest
+      else
+        return HMAC::SHA1.digest(key, text)
+      end
+    end
+
+    def CryptUtil.sha256(text)
+      return Digest::SHA256.digest(text)
+    end
+
+    def CryptUtil.hmac_sha256(key, text)
+      if Digest.const_defined? :HMAC      
+        Digest::HMAC.new(key,Digest::SHA256).update(text).digest
+      else
+        return HMAC::SHA256.digest(key, text)
+      end
+    end
+
+    # Generate a random string of the given length, composed of the
+    # specified characters.  If chars is nil, generate a string
+    # composed of characters in the range 0..255.
+    def CryptUtil.random_string(length, chars=nil)
+      s = ""
+
+      unless chars.nil?
+        length.times { s << chars[rand(chars.length)] }
+      else
+        length.times { s << rand(256).chr }
+      end
+      return s
+    end
+
+    # Convert a number to its binary representation; return a string
+    # of bytes.
+    def CryptUtil.num_to_binary(n)
+      bits = n.to_s(2)
+      prepend = (8 - bits.length % 8)
+      bits = ('0' * prepend) + bits
+      return [bits].pack('B*')
+    end
+
+    # Convert a string of bytes into a number.
+    def CryptUtil.binary_to_num(s)
+      # taken from openid-ruby 0.0.1
+      s = "\000" * (4 - (s.length % 4)) + s
+      num = 0
+      s.unpack('N*').each do |x|
+        num <<= 32
+        num |= x
+      end
+      return num
+    end
+
+    # Encode a number as a base64-encoded byte string.
+    def CryptUtil.num_to_base64(l)
+      return OpenID::Util.to_base64(num_to_binary(l))
+    end
+
+    # Decode a base64 byte string to a number.
+    def CryptUtil.base64_to_num(s)
+      return binary_to_num(OpenID::Util.from_base64(s))
+    end
+  end
+end
diff --git a/vendor/gems/ruby-openid-2.1.4/lib/openid/dh.rb b/vendor/gems/ruby-openid-2.1.4/lib/openid/dh.rb
new file mode 100644 (file)
index 0000000..cbe5311
--- /dev/null
@@ -0,0 +1,89 @@
+require "openid/util"
+require "openid/cryptutil"
+
+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
+
+    # From the OpenID specification
+    @@default_mod = 155172898181473697471232257763715539915724801966915404479707795314057629378541917580651227423698188993727816152646631438561595825688188889951272158842675419950341258706556549803580104870537681476726513255747040765857479291291572334510643245094715007229621094194349783925984760375594985848253359305585439638443
+    @@default_gen = 2
+
+    attr_reader :modulus, :generator, :public
+
+    # A new DiffieHellman object, using the modulus and generator from
+    # the OpenID specification
+    def DiffieHellman.from_defaults
+      DiffieHellman.new(@@default_mod, @@default_gen)
+    end
+
+    def initialize(modulus=nil, generator=nil, priv=nil)
+      @modulus = modulus.nil? ? @@default_mod : modulus
+      @generator = generator.nil? ? @@default_gen : generator
+      set_private(priv.nil? ? OpenID::CryptUtil.rand(@modulus-2) + 1 : priv)
+    end
+
+    def get_shared_secret(composite)
+      DiffieHellman.powermod(composite, @private, @modulus)
+    end
+
+    def xor_secret(algorithm, composite, secret)
+      dh_shared = get_shared_secret(composite)
+      packed_dh_shared = OpenID::CryptUtil.num_to_binary(dh_shared)
+      hashed_dh_shared = algorithm.call(packed_dh_shared)
+      return DiffieHellman.strxor(secret, hashed_dh_shared)
+    end
+
+    def using_default_values?
+      @generator == @@default_gen && @modulus == @@default_mod
+    end
+
+    private
+    def set_private(priv)
+      @private = priv
+      @public = DiffieHellman.powermod(@generator, @private, @modulus)
+    end
+
+    def DiffieHellman.strxor(s, t)
+      if s.length != t.length
+        raise ArgumentError, "strxor: lengths don't match. " +
+          "Inputs were #{s.inspect} and #{t.inspect}"
+      end
+
+      if String.method_defined? :bytes
+        s.bytes.zip(t.bytes).map{|sb,tb| sb^tb}.pack('C*')
+      else
+        indices = 0...(s.length)
+        chrs = indices.collect {|i| (s[i]^t[i]).chr}
+        chrs.join("")
+      end
+    end
+
+    # This code is taken from this post:
+    # <http://blade.nagaokaut.ac.jp/cgi-bin/scat.\rb/ruby/ruby-talk/19098>
+    # by Eric Lee Green.
+    def DiffieHellman.powermod(x, n, q)
+      counter=0
+      n_p=n
+      y_p=1
+      z_p=x
+      while n_p != 0
+        if n_p[0]==1
+          y_p=(y_p*z_p) % q
+        end
+        n_p = n_p >> 1
+        z_p = (z_p * z_p) % q
+        counter += 1
+      end
+      return y_p
+    end
+
+  end
+
+end
diff --git a/vendor/gems/ruby-openid-2.1.4/lib/openid/extension.rb b/vendor/gems/ruby-openid-2.1.4/lib/openid/extension.rb
new file mode 100644 (file)
index 0000000..f0f02bb
--- /dev/null
@@ -0,0 +1,39 @@
+require 'openid/message'
+
+module OpenID
+  # An interface for OpenID extensions.
+  class Extension < Object
+
+    def initialize
+      @ns_uri = nil
+      @ns_alias = nil
+    end
+
+    # Get the string arguments that should be added to an OpenID
+    # message for this extension.
+    def get_extension_args
+      raise NotImplementedError
+    end
+
+    # Add the arguments from this extension to the provided
+    # message, or create a new message containing only those
+    # arguments.  Returns the message with added extension args.
+    def to_message(message = nil)
+      if message.nil?
+#         warnings.warn('Passing None to Extension.toMessage is deprecated. '
+#                       'Creating a message assuming you want OpenID 2.',
+#                       DeprecationWarning, stacklevel=2)
+        Message.new(OPENID2_NS)
+      end
+      message = Message.new if message.nil?
+
+      implicit = message.is_openid1()
+
+      message.namespaces.add_alias(@ns_uri, @ns_alias, implicit)
+      # XXX python ignores keyerror if m.ns.getAlias(uri) == alias
+
+      message.update_args(@ns_uri, get_extension_args)
+      return message
+    end
+  end
+end
diff --git a/vendor/gems/ruby-openid-2.1.4/lib/openid/extensions/ax.rb b/vendor/gems/ruby-openid-2.1.4/lib/openid/extensions/ax.rb
new file mode 100644 (file)
index 0000000..55eda8e
--- /dev/null
@@ -0,0 +1,516 @@
+# Implements the OpenID attribute exchange specification, version 1.0
+
+require 'openid/extension'
+require 'openid/trustroot'
+require 'openid/message'
+
+module OpenID
+  module AX
+
+    UNLIMITED_VALUES = "unlimited"
+    MINIMUM_SUPPORTED_ALIAS_LENGTH = 32
+
+    # check alias for invalid characters, raise AXError if found
+    def self.check_alias(name)
+      if name.match(/(,|\.)/)
+        raise Error, ("Alias #{name.inspect} must not contain a "\
+                      "comma or period.")
+      end
+    end
+
+    # Raised when data does not comply with AX 1.0 specification
+    class Error < ArgumentError
+    end
+
+    # Abstract class containing common code for attribute exchange messages
+    class AXMessage < Extension
+      attr_accessor :ns_alias, :mode, :ns_uri
+
+      NS_URI = 'http://openid.net/srv/ax/1.0'
+      def initialize
+        @ns_alias = 'ax'
+        @ns_uri = NS_URI
+        @mode = nil
+      end
+
+      protected
+
+      # Raise an exception if the mode in the attribute exchange
+      # arguments does not match what is expected for this class.
+      def check_mode(ax_args)
+        actual_mode = ax_args['mode']
+        if actual_mode != @mode
+          raise Error, "Expected mode #{mode.inspect}, got #{actual_mode.inspect}"
+        end
+      end
+
+      def new_args
+        {'mode' => @mode}
+      end
+    end
+
+    # Represents a single attribute in an attribute exchange
+    # request. This should be added to an Request object in order to
+    # request the attribute.
+    #
+    # @ivar required: Whether the attribute will be marked as required
+    #     when presented to the subject of the attribute exchange
+    #     request.
+    # @type required: bool
+    #
+    # @ivar count: How many values of this type to request from the
+    #      subject. Defaults to one.
+    # @type count: int
+    #
+    # @ivar type_uri: The identifier that determines what the attribute
+    #      represents and how it is serialized. For example, one type URI
+    #      representing dates could represent a Unix timestamp in base 10
+    #      and another could represent a human-readable string.
+    # @type type_uri: str
+    #
+    # @ivar ns_alias: The name that should be given to this alias in the
+    #      request. If it is not supplied, a generic name will be
+    #      assigned. For example, if you want to call a Unix timestamp
+    #      value 'tstamp', set its alias to that value. If two attributes
+    #      in the same message request to use the same alias, the request
+    #      will fail to be generated.
+    # @type alias: str or NoneType
+    class AttrInfo < Object
+      attr_reader :type_uri, :count, :ns_alias
+      attr_accessor :required
+      def initialize(type_uri, ns_alias=nil, required=false, count=1)
+        @type_uri = type_uri
+        @count = count
+        @required = required
+        @ns_alias = ns_alias
+      end
+
+      def wants_unlimited_values?
+        @count == UNLIMITED_VALUES
+      end
+    end
+
+    # Given a namespace mapping and a string containing a
+    # comma-separated list of namespace aliases, return a list of type
+    # URIs that correspond to those aliases.
+    # namespace_map: OpenID::NamespaceMap
+    def self.to_type_uris(namespace_map, alias_list_s)
+      return [] if alias_list_s.nil?
+      alias_list_s.split(',').inject([]) {|uris, name|
+        type_uri = namespace_map.get_namespace_uri(name)
+        raise IndexError, "No type defined for attribute name #{name.inspect}" if type_uri.nil?
+        uris << type_uri
+      }
+    end
+
+
+    # An attribute exchange 'fetch_request' message. This message is
+    # sent by a relying party when it wishes to obtain attributes about
+    # the subject of an OpenID authentication request.
+    class FetchRequest < AXMessage
+      attr_reader :requested_attributes
+      attr_accessor :update_url
+
+      def initialize(update_url = nil)
+        super()
+        @mode = 'fetch_request'
+        @requested_attributes = {}
+        @update_url = update_url
+      end
+
+      # Add an attribute to this attribute exchange request.
+      # attribute: AttrInfo, the attribute being requested
+      # Raises IndexError if the requested attribute is already present
+      #   in this request.
+      def add(attribute)
+        if @requested_attributes[attribute.type_uri]
+          raise IndexError, "The attribute #{attribute.type_uri} has already been requested"
+        end
+        @requested_attributes[attribute.type_uri] = attribute
+      end
+
+      # Get the serialized form of this attribute fetch request.
+      # returns a hash of the arguments
+      def get_extension_args
+        aliases = NamespaceMap.new
+        required = []
+        if_available = []
+        ax_args = new_args 
+        @requested_attributes.each{|type_uri, attribute|
+          if attribute.ns_alias
+            name = aliases.add_alias(type_uri, attribute.ns_alias)
+          else
+            name = aliases.add(type_uri)
+          end
+          if attribute.required
+            required << name
+          else
+            if_available << name
+          end
+          if attribute.count != 1
+            ax_args["count.#{name}"] = attribute.count.to_s
+          end
+          ax_args["type.#{name}"] = type_uri
+        }
+
+        unless required.empty?
+          ax_args['required'] = required.join(',')
+        end
+        unless if_available.empty?
+          ax_args['if_available'] = if_available.join(',')
+        end
+        return ax_args
+      end
+
+      # Get the type URIs for all attributes that have been marked
+      # as required.
+      def get_required_attrs
+        @requested_attributes.inject([]) {|required, (type_uri, attribute)|
+          if attribute.required
+            required << type_uri
+          else
+            required
+          end
+        }
+      end
+
+      # Extract a FetchRequest from an OpenID message
+      # message: OpenID::Message
+      # return a FetchRequest or nil if AX arguments are not present
+      def self.from_openid_request(oidreq)
+        message = oidreq.message
+        ax_args = message.get_args(NS_URI)
+        return nil if ax_args == {}
+        req = new
+        req.parse_extension_args(ax_args)
+
+        if req.update_url
+          realm = message.get_arg(OPENID_NS, 'realm',
+                                  message.get_arg(OPENID_NS, 'return_to'))
+          if realm.nil? or realm.empty?
+            raise Error, "Cannot validate update_url #{req.update_url.inspect} against absent realm"
+          end
+          tr = TrustRoot::TrustRoot.parse(realm)
+          unless tr.validate_url(req.update_url)
+            raise Error, "Update URL #{req.update_url.inspect} failed validation against realm #{realm.inspect}"
+          end
+        end
+
+        return req
+      end
+
+      def parse_extension_args(ax_args)
+        check_mode(ax_args)
+
+        aliases = NamespaceMap.new
+
+        ax_args.each{|k,v|
+          if k.index('type.') == 0
+            name = k[5..-1]
+            type_uri = v
+            aliases.add_alias(type_uri, name)
+
+            count_key = 'count.'+name
+            count_s = ax_args[count_key]
+            count = 1
+            if count_s
+              if count_s == UNLIMITED_VALUES
+                count = count_s
+              else
+                count = count_s.to_i
+                if count <= 0
+                  raise Error, "Invalid value for count #{count_key.inspect}: #{count_s.inspect}"
+                end
+              end
+            end
+            add(AttrInfo.new(type_uri, name, false, count))
+          end
+        }
+
+        required = AX.to_type_uris(aliases, ax_args['required'])
+        required.each{|type_uri|
+          @requested_attributes[type_uri].required = true
+        }
+        if_available = AX.to_type_uris(aliases, ax_args['if_available'])
+        all_type_uris = required + if_available
+
+        aliases.namespace_uris.each{|type_uri|
+          unless all_type_uris.member? type_uri
+            raise Error, "Type URI #{type_uri.inspect} was in the request but not present in 'required' or 'if_available'"
+          end
+        }
+        @update_url = ax_args['update_url']
+      end
+
+      # return the list of AttrInfo objects contained in the FetchRequest
+      def attributes
+        @requested_attributes.values
+      end
+
+      # return the list of requested attribute type URIs
+      def requested_types
+        @requested_attributes.keys
+      end
+
+      def member?(type_uri)
+        ! @requested_attributes[type_uri].nil?
+      end
+
+    end
+
+    # Abstract class that implements a message that has attribute
+    # keys and values. It contains the common code between
+    # fetch_response and store_request.
+    class KeyValueMessage < AXMessage
+      attr_reader :data
+      def initialize
+        super()
+        @mode = nil
+        @data = {}
+        @data.default = []
+      end
+
+      # Add a single value for the given attribute type to the
+      # message. If there are already values specified for this type,
+      # this value will be sent in addition to the values already
+      # specified.
+      def add_value(type_uri, value)
+        @data[type_uri] = @data[type_uri] << value
+      end
+
+      # Set the values for the given attribute type. This replaces
+      # any values that have already been set for this attribute.
+      def set_values(type_uri, values)
+        @data[type_uri] = values
+      end
+
+      # Get the extension arguments for the key/value pairs
+      # contained in this message.
+      def _get_extension_kv_args(aliases = nil)
+        aliases = NamespaceMap.new if aliases.nil?
+
+        ax_args = new_args
+
+        @data.each{|type_uri, values|
+          name = aliases.add(type_uri)
+          ax_args['type.'+name] = type_uri
+          ax_args['count.'+name] = values.size.to_s
+
+          values.each_with_index{|value, i|
+            key = "value.#{name}.#{i+1}"
+            ax_args[key] = value
+          }
+        }
+        return ax_args
+      end
+
+      # Parse attribute exchange key/value arguments into this object.
+
+      def parse_extension_args(ax_args)
+        check_mode(ax_args)
+        aliases = NamespaceMap.new
+
+        ax_args.each{|k, v|
+          if k.index('type.') == 0
+            type_uri = v
+            name = k[5..-1]
+
+            AX.check_alias(name)
+            aliases.add_alias(type_uri,name)
+          end
+        }
+
+        aliases.each{|type_uri, name|
+          count_s = ax_args['count.'+name]
+          count = count_s.to_i
+          if count_s.nil?
+            value = ax_args['value.'+name]
+            if value.nil?
+              raise IndexError, "Missing #{'value.'+name} in FetchResponse" 
+            elsif value.empty?
+              values = []
+            else
+              values = [value]
+            end
+          elsif count_s.to_i == 0
+            values = []
+          else
+            values = (1..count).inject([]){|l,i|
+              key = "value.#{name}.#{i}"
+              v = ax_args[key]
+              raise IndexError, "Missing #{key} in FetchResponse" if v.nil?
+              l << v
+            }
+          end
+          @data[type_uri] = values
+        }
+      end
+
+      # Get a single value for an attribute. If no value was sent
+      # for this attribute, use the supplied default. If there is more
+      # than one value for this attribute, this method will fail.
+      def get_single(type_uri, default = nil)
+        values = @data[type_uri]
+        return default if values.empty?
+        if values.size != 1
+          raise Error, "More than one value present for #{type_uri.inspect}"
+        else
+          return values[0]
+        end
+      end
+
+      # retrieve the list of values for this attribute
+      def get(type_uri)
+        @data[type_uri]
+      end
+      
+      # retrieve the list of values for this attribute
+      def [](type_uri)
+        @data[type_uri]
+      end
+
+      # get the number of responses for this attribute
+      def count(type_uri)
+        @data[type_uri].size
+      end
+
+    end
+
+    # A fetch_response attribute exchange message
+    class FetchResponse < KeyValueMessage
+      attr_reader :update_url
+
+      def initialize(update_url = nil)
+        super()
+        @mode = 'fetch_response'
+        @update_url = update_url
+      end
+
+      # Serialize this object into arguments in the attribute
+      # exchange namespace
+      # Takes an optional FetchRequest.  If specified, the response will be
+      # validated against this request, and empty responses for requested
+      # fields with no data will be sent.
+      def get_extension_args(request = nil)
+        aliases = NamespaceMap.new
+        zero_value_types = []
+
+        if request
+          # Validate the data in the context of the request (the
+          # same attributes should be present in each, and the
+          # counts in the response must be no more than the counts
+          # in the request)
+          @data.keys.each{|type_uri|
+            unless request.member? type_uri
+              raise IndexError, "Response attribute not present in request: #{type_uri.inspect}"
+            end
+          }
+
+          request.attributes.each{|attr_info|
+            # Copy the aliases from the request so that reading
+            # the response in light of the request is easier
+            if attr_info.ns_alias.nil?
+              aliases.add(attr_info.type_uri)
+            else
+              aliases.add_alias(attr_info.type_uri, attr_info.ns_alias)
+            end
+            values = @data[attr_info.type_uri]
+            if values.empty? # @data defaults to []
+              zero_value_types << attr_info
+            end
+            if attr_info.count != UNLIMITED_VALUES and attr_info.count < values.size
+              raise Error, "More than the number of requested values were specified for #{attr_info.type_uri.inspect}"
+            end
+          }
+        end
+
+        kv_args = _get_extension_kv_args(aliases)
+
+        # Add the KV args into the response with the args that are
+        # unique to the fetch_response
+        ax_args = new_args
+
+        zero_value_types.each{|attr_info|
+          name = aliases.get_alias(attr_info.type_uri)
+          kv_args['type.' + name] = attr_info.type_uri
+          kv_args['count.' + name] = '0'
+        }
+        update_url = (request and request.update_url or @update_url)
+        ax_args['update_url'] = update_url unless update_url.nil?
+        ax_args.update(kv_args)
+        return ax_args
+      end
+
+      def parse_extension_args(ax_args)
+        super
+        @update_url = ax_args['update_url']
+      end
+
+      # Construct a FetchResponse object from an OpenID library
+      # SuccessResponse object.
+      def self.from_success_response(success_response, signed=true)
+        obj = self.new
+        if signed
+          ax_args = success_response.get_signed_ns(obj.ns_uri)
+        else
+          ax_args = success_response.message.get_args(obj.ns_uri)
+        end
+
+        begin
+          obj.parse_extension_args(ax_args)
+          return obj
+        rescue Error => e
+          return nil
+        end
+      end
+    end
+
+    # A store request attribute exchange message representation
+    class StoreRequest < KeyValueMessage
+      def initialize
+        super
+        @mode = 'store_request'
+      end
+
+      def get_extension_args(aliases=nil)
+        ax_args = new_args
+        kv_args = _get_extension_kv_args(aliases)
+        ax_args.update(kv_args)
+        return ax_args
+      end
+    end
+
+    # An indication that the store request was processed along with
+    # this OpenID transaction.
+    class StoreResponse < AXMessage
+      SUCCESS_MODE = 'store_response_success'
+      FAILURE_MODE = 'store_response_failure'
+      attr_reader :error_message
+
+      def initialize(succeeded = true, error_message = nil)
+        super()
+        if succeeded and error_message
+          raise Error, "Error message included in a success response"
+        end
+        if succeeded
+          @mode = SUCCESS_MODE
+        else
+          @mode = FAILURE_MODE
+        end
+        @error_message = error_message
+      end
+
+      def succeeded?
+        @mode == SUCCESS_MODE
+      end
+
+      def get_extension_args
+        ax_args = new_args
+        if !succeeded? and error_message
+          ax_args['error'] = @error_message
+        end
+        return ax_args
+      end
+    end
+  end
+end
diff --git a/vendor/gems/ruby-openid-2.1.4/lib/openid/extensions/pape.rb b/vendor/gems/ruby-openid-2.1.4/lib/openid/extensions/pape.rb
new file mode 100644 (file)
index 0000000..0a7413c
--- /dev/null
@@ -0,0 +1,179 @@
+# An implementation of the OpenID Provider Authentication Policy
+# Extension 1.0
+# see: http://openid.net/specs/
+
+require 'openid/extension'
+
+module OpenID
+
+  module PAPE
+    NS_URI = "http://specs.openid.net/extensions/pape/1.0"
+    AUTH_MULTI_FACTOR_PHYSICAL =
+      'http://schemas.openid.net/pape/policies/2007/06/multi-factor-physical'
+    AUTH_MULTI_FACTOR =
+      'http://schemas.openid.net/pape/policies/2007/06/multi-factor'
+    AUTH_PHISHING_RESISTANT =
+      'http://schemas.openid.net/pape/policies/2007/06/phishing-resistant'
+    TIME_VALIDATOR = /\d\d\d\d-\d\d-\d\dT\d\d:\d\d:\d\dZ/
+    # A Provider Authentication Policy request, sent from a relying
+    # party to a provider
+    class Request < Extension
+      attr_accessor :preferred_auth_policies, :max_auth_age, :ns_alias, :ns_uri
+      def initialize(preferred_auth_policies=[], max_auth_age=nil)
+        @ns_alias = 'pape'
+        @ns_uri = NS_URI
+        @preferred_auth_policies = preferred_auth_policies
+        @max_auth_age = max_auth_age
+      end
+
+      # Add an acceptable authentication policy URI to this request
+      # This method is intended to be used by the relying party to add
+      # acceptable authentication types to the request.
+      def add_policy_uri(policy_uri)
+        unless @preferred_auth_policies.member? policy_uri
+          @preferred_auth_policies << policy_uri
+        end
+      end
+
+      def get_extension_args
+        ns_args = {
+          'preferred_auth_policies' => @preferred_auth_policies.join(' ')
+        }
+        ns_args['max_auth_age'] = @max_auth_age.to_s if @max_auth_age
+        return ns_args
+      end
+
+      # Instantiate a Request object from the arguments in a
+      # checkid_* OpenID message
+      # return nil if the extension was not requested.
+      def self.from_openid_request(oid_req)
+        pape_req = new
+        args = oid_req.message.get_args(NS_URI)
+        if args == {}
+          return nil
+        end
+        pape_req.parse_extension_args(args)
+        return pape_req
+      end
+
+      # Set the state of this request to be that expressed in these
+      # PAPE arguments
+      def parse_extension_args(args)
+        @preferred_auth_policies = []
+        policies_str = args['preferred_auth_policies']
+        if policies_str
+          policies_str.split(' ').each{|uri|
+            add_policy_uri(uri)
+          }
+        end
+
+        max_auth_age_str = args['max_auth_age']
+        if max_auth_age_str
+          @max_auth_age = max_auth_age_str.to_i
+        else
+          @max_auth_age = nil
+        end
+      end
+
+      # Given a list of authentication policy URIs that a provider
+      # supports, this method returns the subset of those types
+      # that are preferred by the relying party.
+      def preferred_types(supported_types)
+        @preferred_auth_policies.select{|uri| supported_types.member? uri}
+      end
+    end
+
+    # A Provider Authentication Policy response, sent from a provider
+    # to a relying party
+    class Response < Extension
+      attr_accessor :ns_alias, :auth_policies, :auth_time, :nist_auth_level
+      def initialize(auth_policies=[], auth_time=nil, nist_auth_level=nil)
+        @ns_alias = 'pape'
+        @ns_uri = NS_URI
+        @auth_policies = auth_policies
+        @auth_time = auth_time
+        @nist_auth_level = nist_auth_level
+      end
+
+      # Add a policy URI to the response
+      # see http://openid.net/specs/openid-provider-authentication-policy-extension-1_0-01.html#auth_policies
+      def add_policy_uri(policy_uri)
+        @auth_policies << policy_uri unless @auth_policies.member?(policy_uri)
+      end
+
+      # Create a Response object from an OpenID::Consumer::SuccessResponse
+      def self.from_success_response(success_response)
+        args = success_response.get_signed_ns(NS_URI)
+        return nil if args.nil?
+        pape_resp = new
+        pape_resp.parse_extension_args(args)
+        return pape_resp
+      end
+
+      # parse the provider authentication policy arguments into the
+      # internal state of this object
+      # if strict is specified, raise an exception when bad data is
+      # encountered
+      def parse_extension_args(args, strict=false)
+        policies_str = args['auth_policies']
+        if policies_str and policies_str != 'none'
+          @auth_policies = policies_str.split(' ')
+        end
+
+        nist_level_str = args['nist_auth_level']
+        if nist_level_str
+          # special handling of zero to handle to_i behavior
+          if nist_level_str.strip == '0'
+            nist_level = 0
+          else
+            nist_level = nist_level_str.to_i
+            # if it's zero here we have a bad value
+            if nist_level == 0
+              nist_level = nil
+            end
+          end
+          if nist_level and nist_level >= 0 and nist_level < 5
+            @nist_auth_level = nist_level
+          elsif strict
+            raise ArgumentError, "nist_auth_level must be an integer 0 through 4, not #{nist_level_str.inspect}"
+          end
+        end
+
+        auth_time_str = args['auth_time']
+        if auth_time_str
+          # validate time string
+          if auth_time_str =~ TIME_VALIDATOR
+            @auth_time = auth_time_str
+          elsif strict
+            raise ArgumentError, "auth_time must be in RFC3339 format"
+          end
+        end
+      end
+
+      def get_extension_args
+        ns_args = {}
+        if @auth_policies.empty?
+          ns_args['auth_policies'] = 'none'
+        else
+          ns_args['auth_policies'] = @auth_policies.join(' ')
+        end
+        if @nist_auth_level
+          unless (0..4).member? @nist_auth_level
+            raise ArgumentError, "nist_auth_level must be an integer 0 through 4, not #{@nist_auth_level.inspect}"
+          end
+          ns_args['nist_auth_level'] = @nist_auth_level.to_s
+        end
+
+        if @auth_time
+          unless @auth_time =~ TIME_VALIDATOR
+            raise ArgumentError, "auth_time must be in RFC3339 format"
+          end
+          ns_args['auth_time'] = @auth_time
+        end
+        return ns_args
+      end
+
+    end
+  end
+
+end
diff --git a/vendor/gems/ruby-openid-2.1.4/lib/openid/extensions/sreg.rb b/vendor/gems/ruby-openid-2.1.4/lib/openid/extensions/sreg.rb
new file mode 100644 (file)
index 0000000..8dc780e
--- /dev/null
@@ -0,0 +1,277 @@
+require 'openid/extension'
+require 'openid/util'
+require 'openid/message'
+
+module OpenID
+  module SReg
+    DATA_FIELDS = {
+      'fullname'=>'Full Name',
+      'nickname'=>'Nickname',
+      'dob'=>'Date of Birth',
+      'email'=>'E-mail Address',
+      'gender'=>'Gender',
+      'postcode'=>'Postal Code',
+      'country'=>'Country',
+      'language'=>'Language',
+      'timezone'=>'Time Zone',
+    }
+
+    NS_URI_1_0 = 'http://openid.net/sreg/1.0'
+    NS_URI_1_1 = 'http://openid.net/extensions/sreg/1.1'
+    NS_URI = NS_URI_1_1
+
+    begin
+      Message.register_namespace_alias(NS_URI_1_1, 'sreg')
+    rescue NamespaceAliasRegistrationError => e
+      Util.log(e)
+    end
+
+    # raise ArgumentError if fieldname is not in the defined sreg fields
+    def OpenID.check_sreg_field_name(fieldname)
+      unless DATA_FIELDS.member? fieldname
+        raise ArgumentError, "#{fieldname} is not a defined simple registration field"
+      end
+    end
+
+    # Does the given endpoint advertise support for simple registration?
+    def OpenID.supports_sreg?(endpoint)
+      endpoint.uses_extension(NS_URI_1_1) || endpoint.uses_extension(NS_URI_1_0)
+    end
+
+    # Extract the simple registration namespace URI from the given
+    # OpenID message. Handles OpenID 1 and 2, as well as both sreg
+    # namespace URIs found in the wild, as well as missing namespace
+    # definitions (for OpenID 1)
+    def OpenID.get_sreg_ns(message)
+      [NS_URI_1_1, NS_URI_1_0].each{|ns|
+        if message.namespaces.get_alias(ns)
+          return ns
+        end
+      }
+      # try to add an alias, since we didn't find one
+      ns = NS_URI_1_1
+      begin
+        message.namespaces.add_alias(ns, 'sreg')
+      rescue IndexError
+        raise NamespaceError
+      end
+      return ns
+    end
+
+    # The simple registration namespace was not found and could not
+    # be created using the expected name (there's another extension
+    # using the name 'sreg')
+    #
+    # This is not <em>illegal</em>, for OpenID 2, although it probably
+    # indicates a problem, since it's not expected that other extensions
+    # will re-use the alias that is in use for OpenID 1.
+    #
+    # If this is an OpenID 1 request, then there is no recourse. This
+    # should not happen unless some code has modified the namespaces for
+    # the message that is being processed.
+    class NamespaceError < ArgumentError
+    end
+
+    # An object to hold the state of a simple registration request.
+    class Request < Extension
+      attr_reader :optional, :required, :ns_uri
+      attr_accessor :policy_url
+      def initialize(required = nil, optional = nil, policy_url = nil, ns_uri = NS_URI)
+        super()
+
+        @policy_url = policy_url
+        @ns_uri = ns_uri
+        @ns_alias = 'sreg'
+        @required = []
+        @optional = []
+
+        if required
+          request_fields(required, true, true)
+        end
+        if optional
+          request_fields(optional, false, true)
+        end
+      end
+
+      # Create a simple registration request that contains the
+      # fields that were requested in the OpenID request with the
+      # given arguments
+      # Takes an OpenID::CheckIDRequest, returns an OpenID::Sreg::Request
+      # return nil if the extension was not requested.
+      def self.from_openid_request(request)
+        # Since we're going to mess with namespace URI mapping, don't
+        # mutate the object that was passed in.
+        message = request.message.copy
+        ns_uri = OpenID::get_sreg_ns(message)
+        args = message.get_args(ns_uri)
+        return nil if args == {}
+        req = new(nil,nil,nil,ns_uri)
+        req.parse_extension_args(args)
+        return req
+      end
+
+      # Parse the unqualified simple registration request
+      # parameters and add them to this object.
+      #
+      # This method is essentially the inverse of
+      # getExtensionArgs. This method restores the serialized simple
+      # registration request fields.
+      #
+      # If you are extracting arguments from a standard OpenID
+      # checkid_* request, you probably want to use fromOpenIDRequest,
+      # which will extract the sreg namespace and arguments from the
+      # OpenID request. This method is intended for cases where the
+      # OpenID server needs more control over how the arguments are
+      # parsed than that method provides.
+      def parse_extension_args(args, strict = false)
+        required_items = args['required']
+        unless required_items.nil? or required_items.empty?
+          required_items.split(',').each{|field_name|
+            begin
+              request_field(field_name, true, strict)
+            rescue ArgumentError
+              raise if strict
+            end
+          }
+        end
+
+        optional_items = args['optional']
+        unless optional_items.nil? or optional_items.empty?
+          optional_items.split(',').each{|field_name|
+            begin
+              request_field(field_name, false, strict)
+            rescue ArgumentError
+              raise if strict
+            end
+          }
+        end
+        @policy_url = args['policy_url']
+      end
+
+      # A list of all of the simple registration fields that were
+      # requested, whether they were required or optional.
+      def all_requested_fields
+        @required + @optional
+      end
+
+      # Have any simple registration fields been requested?
+      def were_fields_requested?
+        !all_requested_fields.empty?
+      end
+
+      # Request the specified field from the OpenID user
+      # field_name: the unqualified simple registration field name
+      # required: whether the given field should be presented
+      #        to the user as being a required to successfully complete
+      #        the request
+      # strict: whether to raise an exception when a field is
+      #        added to a request more than once
+      # Raises ArgumentError if the field_name is not a simple registration
+      # field, or if strict is set and a field is added more than once
+      def request_field(field_name, required=false, strict=false)
+        OpenID::check_sreg_field_name(field_name)
+
+        if strict
+          if (@required + @optional).member? field_name
+            raise ArgumentError, 'That field has already been requested'
+          end
+        else
+          return if @required.member? field_name
+          if @optional.member? field_name
+            if required
+              @optional.delete field_name
+            else
+              return
+            end
+          end
+        end
+        if required
+          @required << field_name
+        else
+          @optional << field_name
+        end
+      end
+
+      # Add the given list of fields to the request.
+      def request_fields(field_names, required = false, strict = false)
+        raise ArgumentError unless field_names.respond_to?(:each) and
+                                   field_names[0].is_a?(String)
+        field_names.each{|fn|request_field(fn, required, strict)}
+      end
+
+      # Get a hash of unqualified simple registration arguments
+      # representing this request.
+      # This method is essentially the inverse of parse_extension_args.
+      # This method serializes the simple registration request fields.
+      def get_extension_args
+        args = {}
+        args['required'] = @required.join(',') unless @required.empty?
+        args['optional'] = @optional.join(',') unless @optional.empty?
+        args['policy_url'] = @policy_url unless @policy_url.nil?
+        return args
+      end
+
+      def member?(field_name)
+        all_requested_fields.member?(field_name)
+      end
+
+    end
+
+    # Represents the data returned in a simple registration response
+    # inside of an OpenID id_res response. This object will be
+    # created by the OpenID server, added to the id_res response
+    # object, and then extracted from the id_res message by the Consumer.
+    class Response < Extension
+      attr_reader :ns_uri, :data
+
+      def initialize(data = {}, ns_uri=NS_URI)
+        @ns_alias = 'sreg'
+        @data = data
+        @ns_uri = ns_uri
+      end
+
+      # Take a Request and a hash of simple registration
+      # values and create a Response object containing that data.
+      def self.extract_response(request, data)
+        arf = request.all_requested_fields
+        resp_data = data.reject{|k,v| !arf.member?(k) || v.nil? }
+        new(resp_data, request.ns_uri)
+      end
+
+      # Create an Response object from an
+      # OpenID::Consumer::SuccessResponse from consumer.complete
+      # If you set the signed_only parameter to false, unsigned data from
+      # the id_res message from the server will be processed.
+      def self.from_success_response(success_response, signed_only = true)
+        ns_uri = OpenID::get_sreg_ns(success_response.message)
+        if signed_only
+          args = success_response.get_signed_ns(ns_uri)
+          return nil if args.nil? # No signed args, so fail
+        else
+          args = success_response.message.get_args(ns_uri)
+        end
+        args.reject!{|k,v| !DATA_FIELDS.member?(k) }
+        new(args, ns_uri)
+      end
+
+      # Get the fields to put in the simple registration namespace
+      # when adding them to an id_res message.
+      def get_extension_args
+        return @data
+      end
+
+      # Read-only hashlike interface.
+      # Raises an exception if the field name is bad
+      def [](field_name)
+        OpenID::check_sreg_field_name(field_name)
+        data[field_name]
+      end
+
+      def empty?
+        @data.empty?
+      end
+      # XXX is there more to a hashlike interface I should add?
+    end
+  end
+end
+
diff --git a/vendor/gems/ruby-openid-2.1.4/lib/openid/extras.rb b/vendor/gems/ruby-openid-2.1.4/lib/openid/extras.rb
new file mode 100644 (file)
index 0000000..0d9560a
--- /dev/null
@@ -0,0 +1,11 @@
+class String
+  def starts_with?(other)
+    head = self[0, other.length]
+    head == other
+  end
+
+  def ends_with?(other)
+    tail = self[-1 * other.length, other.length]
+    tail == other
+  end
+end
diff --git a/vendor/gems/ruby-openid-2.1.4/lib/openid/fetchers.rb b/vendor/gems/ruby-openid-2.1.4/lib/openid/fetchers.rb
new file mode 100644 (file)
index 0000000..22c87ac
--- /dev/null
@@ -0,0 +1,238 @@
+require 'net/http'
+require 'openid'
+require 'openid/util'
+
+begin
+  require 'net/https'
+rescue LoadError
+  OpenID::Util.log('WARNING: no SSL support found.  Will not be able ' +
+                   'to fetch HTTPS URLs!')
+  require 'net/http'
+end
+
+MAX_RESPONSE_KB = 1024
+
+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
+  # Our HTTPResponse class extends Net::HTTPResponse with an additional
+  # method, final_url.
+  class HTTPResponse
+    attr_accessor :final_url
+
+    attr_accessor :_response
+
+    def self._from_net_response(response, final_url, headers=nil)
+      me = self.new
+      me._response = response
+      me.final_url = final_url
+      return me
+    end
+
+    def method_missing(method, *args)
+      @_response.send(method, *args)
+    end
+
+    def body=(s)
+      @_response.instance_variable_set('@body', s)
+      # XXX Hack to work around ruby's HTTP library behavior.  @body
+      # is only returned if it has been read from the response
+      # object's socket, but since we're not using a socket in this
+      # case, we need to set the @read flag to true to avoid a bug in
+      # Net::HTTPResponse.stream_check when @socket is nil.
+      @_response.instance_variable_set('@read', true)
+    end
+  end
+
+  class FetchingError < OpenIDError
+  end
+
+  class HTTPRedirectLimitReached < FetchingError
+  end
+
+  class SSLFetchingError < FetchingError
+  end
+
+  @fetcher = nil
+
+  def self.fetch(url, body=nil, headers=nil,
+                 redirect_limit=StandardFetcher::REDIRECT_LIMIT)
+    return fetcher.fetch(url, body, headers, redirect_limit)
+  end
+
+  def self.fetcher
+    if @fetcher.nil?
+      @fetcher = StandardFetcher.new
+    end
+
+    return @fetcher
+  end
+
+  def self.fetcher=(fetcher)
+    @fetcher = fetcher
+  end
+
+  # Set the default fetcher to use the HTTP proxy defined in the environment
+  # variable 'http_proxy'.
+  def self.fetcher_use_env_http_proxy
+    proxy_string = ENV['http_proxy']
+    return unless proxy_string
+
+    proxy_uri = URI.parse(proxy_string)
+    @fetcher = StandardFetcher.new(proxy_uri.host, proxy_uri.port,
+                                   proxy_uri.user, proxy_uri.password)
+  end
+  
+  class StandardFetcher
+
+    USER_AGENT = "ruby-openid/#{OpenID::VERSION} (#{RUBY_PLATFORM})"
+
+    REDIRECT_LIMIT = 5
+    TIMEOUT = 60
+
+    attr_accessor :ca_file
+    attr_accessor :timeout
+
+    # I can fetch through a HTTP proxy; arguments are as for Net::HTTP::Proxy.
+    def initialize(proxy_addr=nil, proxy_port=nil,
+                   proxy_user=nil, proxy_pass=nil)
+      @ca_file = nil
+      @proxy = Net::HTTP::Proxy(proxy_addr, proxy_port, proxy_user, proxy_pass)
+      @timeout = TIMEOUT
+    end
+
+    def supports_ssl?(conn)
+      return conn.respond_to?(:use_ssl=)
+    end
+
+    def make_http(uri)
+      http = @proxy.new(uri.host, uri.port)
+      http.read_timeout = @timeout
+      http.open_timeout = @timeout
+      return http
+    end
+
+    def set_verified(conn, verify)
+      if verify
+        conn.verify_mode = OpenSSL::SSL::VERIFY_PEER
+      else
+        conn.verify_mode = OpenSSL::SSL::VERIFY_NONE
+      end
+    end
+
+    def make_connection(uri)
+      conn = make_http(uri)
+
+      if !conn.is_a?(Net::HTTP)
+        raise RuntimeError, sprintf("Expected Net::HTTP object from make_http; got %s",
+                                    conn.class)
+      end
+
+      if uri.scheme == 'https'
+        if supports_ssl?(conn)
+
+          conn.use_ssl = true
+
+          if @ca_file
+            set_verified(conn, true)
+            conn.ca_file = @ca_file
+          else
+            Util.log("WARNING: making https request to #{uri} without verifying " +
+                     "server certificate; no CA path was specified.")
+            set_verified(conn, false)
+          end
+        else
+          raise RuntimeError, "SSL support not found; cannot fetch #{uri}"
+        end
+      end
+
+      return conn
+    end
+
+    def fetch(url, body=nil, headers=nil, redirect_limit=REDIRECT_LIMIT)
+      unparsed_url = url.dup
+      url = URI::parse(url)
+      if url.nil?
+        raise FetchingError, "Invalid URL: #{unparsed_url}"
+      end
+
+      headers ||= {}
+      headers['User-agent'] ||= USER_AGENT
+
+      begin
+        conn = make_connection(url)
+        response = nil
+
+        response = conn.start {
+          # Check the certificate against the URL's hostname
+          if supports_ssl?(conn) and conn.use_ssl?
+            conn.post_connection_check(url.host)
+          end
+
+          if body.nil?
+            conn.request_get(url.request_uri, headers)
+          else
+            headers["Content-type"] ||= "application/x-www-form-urlencoded"
+            conn.request_post(url.request_uri, body, headers)
+          end
+        }
+      rescue RuntimeError => why
+        raise why
+      rescue OpenSSL::SSL::SSLError => why
+        raise SSLFetchingError, "Error connecting to SSL URL #{url}: #{why}"
+      rescue FetchingError => why
+        raise why
+      rescue Exception => why
+        # Things we've caught here include a Timeout::Error, which descends
+        # from SignalException.
+        raise FetchingError, "Error fetching #{url}: #{why}"
+      end
+
+      case response
+      when Net::HTTPRedirection
+        if redirect_limit <= 0
+          raise HTTPRedirectLimitReached.new(
+            "Too many redirects, not fetching #{response['location']}")
+        end
+        begin
+          return fetch(response['location'], body, headers, redirect_limit - 1)
+        rescue HTTPRedirectLimitReached => e
+          raise e
+        rescue FetchingError => why
+          raise FetchingError, "Error encountered in redirect from #{url}: #{why}"
+        end
+      else
+        return HTTPResponse._from_net_response(response, unparsed_url)
+      end
+    end
+  end
+end
diff --git a/vendor/gems/ruby-openid-2.1.4/lib/openid/kvform.rb b/vendor/gems/ruby-openid-2.1.4/lib/openid/kvform.rb
new file mode 100644 (file)
index 0000000..c534d20
--- /dev/null
@@ -0,0 +1,136 @@
+
+module OpenID
+
+  class KVFormError < Exception
+  end
+
+  module Util
+
+    def Util.seq_to_kv(seq, strict=false)
+      # Represent a sequence of pairs of strings as newline-terminated
+      # key:value pairs. The pairs are generated in the order given.
+      #
+      # @param seq: The pairs
+      #
+      # returns a string representation of the sequence
+      err = lambda { |msg|
+        msg = "seq_to_kv warning: #{msg}: #{seq.inspect}"
+        if strict
+          raise KVFormError, msg
+        else
+          Util.log(msg)
+        end
+      }
+
+      lines = []
+      seq.each { |k, v|
+        if !k.is_a?(String)
+          err.call("Converting key to string: #{k.inspect}")
+          k = k.to_s
+        end
+
+        if !k.index("\n").nil?
+          raise KVFormError, "Invalid input for seq_to_kv: key contains newline: #{k.inspect}"
+        end
+
+        if !k.index(":").nil?
+          raise KVFormError, "Invalid input for seq_to_kv: key contains colon: #{k.inspect}"
+        end
+
+        if k.strip() != k
+          err.call("Key has whitespace at beginning or end: #{k.inspect}")
+        end
+
+        if !v.is_a?(String)
+          err.call("Converting value to string: #{v.inspect}")
+          v = v.to_s
+        end
+
+        if !v.index("\n").nil?
+          raise KVFormError, "Invalid input for seq_to_kv: value contains newline: #{v.inspect}"
+        end
+
+        if v.strip() != v
+          err.call("Value has whitespace at beginning or end: #{v.inspect}")
+        end
+
+        lines << k + ":" + v + "\n"
+      }
+
+      return lines.join("")
+    end
+
+    def Util.kv_to_seq(data, strict=false)
+      # After one parse, seq_to_kv and kv_to_seq are inverses, with no
+      # warnings:
+      #
+      # seq = kv_to_seq(s)
+      # seq_to_kv(kv_to_seq(seq)) == seq
+      err = lambda { |msg|
+        msg = "kv_to_seq warning: #{msg}: #{data.inspect}"
+        if strict
+          raise KVFormError, msg
+        else
+          Util.log(msg)
+        end
+      }
+
+      lines = data.split("\n")
+      if data.length == 0
+        return []
+      end
+
+      if data[-1].chr != "\n"
+        err.call("Does not end in a newline")
+        # We don't expect the last element of lines to be an empty
+        # string because split() doesn't behave that way.
+      end
+
+      pairs = []
+      line_num = 0
+      lines.each { |line|
+        line_num += 1
+
+        # Ignore blank lines
+        if line.strip() == ""
+          next
+        end
+
+        pair = line.split(':', 2)
+        if pair.length == 2
+          k, v = pair
+          k_s = k.strip()
+          if k_s != k
+            msg = "In line #{line_num}, ignoring leading or trailing whitespace in key #{k.inspect}"
+            err.call(msg)
+          end
+
+          if k_s.length == 0
+            err.call("In line #{line_num}, got empty key")
+          end
+
+          v_s = v.strip()
+          if v_s != v
+            msg = "In line #{line_num}, ignoring leading or trailing whitespace in value #{v.inspect}"
+            err.call(msg)
+          end
+
+          pairs << [k_s, v_s]
+        else
+          err.call("Line #{line_num} does not contain a colon")
+        end
+      }
+
+      return pairs
+    end
+
+    def Util.dict_to_kv(d)
+      return seq_to_kv(d.entries.sort)
+    end
+
+    def Util.kv_to_dict(s)
+      seq = kv_to_seq(s)
+      return Hash[*seq.flatten]
+    end
+  end
+end
diff --git a/vendor/gems/ruby-openid-2.1.4/lib/openid/kvpost.rb b/vendor/gems/ruby-openid-2.1.4/lib/openid/kvpost.rb
new file mode 100644 (file)
index 0000000..1495afe
--- /dev/null
@@ -0,0 +1,58 @@
+require "openid/message"
+require "openid/fetchers"
+
+module OpenID
+  # Exception that is raised when the server returns a 400 response
+  # code to a direct request.
+  class ServerError < OpenIDError
+    attr_reader :error_text, :error_code, :message
+
+    def initialize(error_text, error_code, message)
+      super(error_text)
+      @error_text = error_text
+      @error_code = error_code
+      @message = message
+    end
+
+    def self.from_message(msg)
+      error_text = msg.get_arg(OPENID_NS, 'error',
+                               '<no error message supplied>')
+      error_code = msg.get_arg(OPENID_NS, 'error_code')
+      return self.new(error_text, error_code, msg)
+    end
+  end
+
+  class KVPostNetworkError < OpenIDError
+  end
+  class HTTPStatusError < OpenIDError
+  end
+
+  class Message
+    def self.from_http_response(response, server_url)
+      msg = self.from_kvform(response.body)
+      case response.code.to_i
+      when 200
+        return msg
+      when 206
+        return msg
+      when 400
+        raise ServerError.from_message(msg)
+      else
+        error_message = "bad status code from server #{server_url}: "\
+        "#{response.code}"
+        raise HTTPStatusError.new(error_message)
+      end
+    end
+  end
+
+  # Send the message to the server via HTTP POST and receive and parse
+  # a response in KV Form
+  def self.make_kv_post(request_message, server_url)
+    begin
+      http_response = self.fetch(server_url, request_message.to_url_encoded)
+    rescue Exception
+      raise KVPostNetworkError.new("Unable to contact OpenID server: #{$!.to_s}")
+    end
+    return Message.from_http_response(http_response, server_url)
+  end
+end
diff --git a/vendor/gems/ruby-openid-2.1.4/lib/openid/message.rb b/vendor/gems/ruby-openid-2.1.4/lib/openid/message.rb
new file mode 100644 (file)
index 0000000..8700378
--- /dev/null
@@ -0,0 +1,553 @@
+require 'openid/util'
+require 'openid/kvform'
+
+module OpenID
+
+  IDENTIFIER_SELECT = 'http://specs.openid.net/auth/2.0/identifier_select'
+
+  # URI for Simple Registration extension, the only commonly deployed
+  # OpenID 1.x extension, and so a special case.
+  SREG_URI = 'http://openid.net/sreg/1.0'
+
+  # The OpenID 1.x namespace URIs
+  OPENID1_NS = 'http://openid.net/signon/1.0'
+  OPENID11_NS = 'http://openid.net/signon/1.1'
+  OPENID1_NAMESPACES = [OPENID1_NS, OPENID11_NS]
+
+  # The OpenID 2.0 namespace URI
+  OPENID2_NS = 'http://specs.openid.net/auth/2.0'
+
+  # The namespace consisting of pairs with keys that are prefixed with
+  # "openid." but not in another namespace.
+  NULL_NAMESPACE = :null_namespace
+
+  # The null namespace, when it is an allowed OpenID namespace
+  OPENID_NS = :openid_namespace
+
+  # The top-level namespace, excluding all pairs with keys that start
+  # with "openid."
+  BARE_NS = :bare_namespace
+
+  # Limit, in bytes, of identity provider and return_to URLs,
+  # including response payload.  See OpenID 1.1 specification,
+  # Appendix D.
+  OPENID1_URL_LIMIT = 2047
+
+  # All OpenID protocol fields.  Used to check namespace aliases.
+  OPENID_PROTOCOL_FIELDS = [
+                            'ns', 'mode', 'error', 'return_to',
+                            'contact', 'reference', 'signed',
+                            'assoc_type', 'session_type',
+                            'dh_modulus', 'dh_gen',
+                            'dh_consumer_public', 'claimed_id',
+                            'identity', 'realm', 'invalidate_handle',
+                            'op_endpoint', 'response_nonce', 'sig',
+                            'assoc_handle', 'trust_root', 'openid',
+                           ]
+
+  # Sentinel used for Message implementation to indicate that getArg
+  # should raise an exception instead of returning a default.
+  NO_DEFAULT = :no_default
+
+  # Raised if the generic OpenID namespace is accessed when there
+  # is no OpenID namespace set for this message.
+  class UndefinedOpenIDNamespace < Exception; end
+
+  # Raised when an alias or namespace URI has already been registered.
+  class NamespaceAliasRegistrationError < Exception; end
+
+  # Raised if openid.ns is not a recognized value.
+  # See Message class variable @@allowed_openid_namespaces
+  class InvalidOpenIDNamespace < Exception; end
+
+  class Message
+    attr_reader :namespaces
+
+    # Raised when key lookup fails
+    class KeyNotFound < IndexError ; end
+
+    # Namespace / alias registration map.  See
+    # register_namespace_alias.
+    @@registered_aliases = {}
+
+    # Registers a (namespace URI, alias) mapping in a global namespace
+    # alias map.  Raises NamespaceAliasRegistrationError if either the
+    # namespace URI or alias has already been registered with a
+    # different value.  This function is required if you want to use a
+    # namespace with an OpenID 1 message.
+    def Message.register_namespace_alias(namespace_uri, alias_)
+      if @@registered_aliases[alias_] == namespace_uri
+          return
+      end
+
+      if @@registered_aliases.values.include?(namespace_uri)
+        raise NamespaceAliasRegistrationError,
+          'Namespace uri #{namespace_uri} already registered'
+      end
+
+      if @@registered_aliases.member?(alias_)
+        raise NamespaceAliasRegistrationError,
+          'Alias #{alias_} already registered'
+      end
+
+      @@registered_aliases[alias_] = namespace_uri
+    end
+
+    @@allowed_openid_namespaces = [OPENID1_NS, OPENID2_NS, OPENID11_NS]
+
+    # Raises InvalidNamespaceError if you try to instantiate a Message
+    # with a namespace not in the above allowed list
+    def initialize(openid_namespace=nil)
+      @args = {}
+      @namespaces = NamespaceMap.new
+      if openid_namespace
+        implicit = OPENID1_NAMESPACES.member? openid_namespace
+        self.set_openid_namespace(openid_namespace, implicit)
+      else
+        @openid_ns_uri = nil
+      end
+    end
+
+    # Construct a Message containing a set of POST arguments.
+    # Raises InvalidNamespaceError if you try to instantiate a Message
+    # with a namespace not in the above allowed list
+    def Message.from_post_args(args)
+      m = Message.new
+      openid_args = {}
+      args.each do |key,value|
+        if value.is_a?(Array)
+          raise ArgumentError, "Query dict must have one value for each key, " +
+            "not lists of values.  Query is #{args.inspect}"
+        end
+
+        prefix, rest = key.split('.', 2)
+
+        if prefix != 'openid' or rest.nil?
+          m.set_arg(BARE_NS, key, value)
+        else
+          openid_args[rest] = value
+        end
+      end
+
+      m._from_openid_args(openid_args)
+      return m
+    end
+
+    # Construct a Message from a parsed KVForm message.
+    # Raises InvalidNamespaceError if you try to instantiate a Message
+    # with a namespace not in the above allowed list
+    def Message.from_openid_args(openid_args)
+      m = Message.new
+      m._from_openid_args(openid_args)
+      return m
+    end
+
+    # Raises InvalidNamespaceError if you try to instantiate a Message
+    # with a namespace not in the above allowed list
+    def _from_openid_args(openid_args)
+      ns_args = []
+
+      # resolve namespaces
+      openid_args.each { |rest, value|
+        ns_alias, ns_key = rest.split('.', 2)
+        if ns_key.nil?
+          ns_alias = NULL_NAMESPACE
+          ns_key = rest
+        end
+
+        if ns_alias == 'ns'
+          @namespaces.add_alias(value, ns_key)
+        elsif ns_alias == NULL_NAMESPACE and ns_key == 'ns'
+          set_openid_namespace(value, false)
+        else
+          ns_args << [ns_alias, ns_key, value]
+        end
+      }
+
+      # implicitly set an OpenID 1 namespace
+      unless get_openid_namespace
+        set_openid_namespace(OPENID1_NS, true)
+      end
+
+      # put the pairs into the appropriate namespaces
+      ns_args.each { |ns_alias, ns_key, value|
+        ns_uri = @namespaces.get_namespace_uri(ns_alias)
+        unless ns_uri
+          ns_uri = _get_default_namespace(ns_alias)
+          unless ns_uri
+            ns_uri = get_openid_namespace
+            ns_key = "#{ns_alias}.#{ns_key}"
+          else
+            @namespaces.add_alias(ns_uri, ns_alias, true)
+          end
+        end
+        self.set_arg(ns_uri, ns_key, value)
+      }
+    end
+
+    def _get_default_namespace(mystery_alias)
+      # only try to map an alias to a default if it's an
+      # OpenID 1.x namespace
+      if is_openid1
+        @@registered_aliases[mystery_alias]
+      end
+    end
+
+    def set_openid_namespace(openid_ns_uri, implicit)
+      if !@@allowed_openid_namespaces.include?(openid_ns_uri)
+        raise InvalidOpenIDNamespace, "Invalid null namespace: #{openid_ns_uri}"
+      end
+      @namespaces.add_alias(openid_ns_uri, NULL_NAMESPACE, implicit)
+      @openid_ns_uri = openid_ns_uri
+    end
+
+    def get_openid_namespace
+      return @openid_ns_uri
+    end
+
+    def is_openid1
+      return OPENID1_NAMESPACES.member?(@openid_ns_uri)
+    end
+
+    def is_openid2
+      return @openid_ns_uri == OPENID2_NS
+    end
+
+    # Create a message from a KVForm string
+    def Message.from_kvform(kvform_string)
+      return Message.from_openid_args(Util.kv_to_dict(kvform_string))
+    end
+
+    def copy
+      return Marshal.load(Marshal.dump(self))
+    end
+
+    # Return all arguments with "openid." in from of namespaced arguments.
+    def to_post_args
+      args = {}
+
+      # add namespace defs to the output
+      @namespaces.each { |ns_uri, ns_alias|
+        if @namespaces.implicit?(ns_uri)
+          next
+        end
+        if ns_alias == NULL_NAMESPACE
+          ns_key = 'openid.ns'
+        else
+          ns_key = 'openid.ns.' + ns_alias
+        end
+        args[ns_key] = ns_uri
+      }
+
+      @args.each { |k, value|
+        ns_uri, ns_key = k
+        key = get_key(ns_uri, ns_key)
+        args[key] = value
+      }
+
+      return args
+    end
+
+    # Return all namespaced arguments, failing if any non-namespaced arguments
+    # exist.
+    def to_args
+      post_args = self.to_post_args
+      kvargs = {}
+      post_args.each { |k,v|
+        if !k.starts_with?('openid.')
+          raise ArgumentError, "This message can only be encoded as a POST, because it contains arguments that are not prefixed with 'openid.'"
+        else
+          kvargs[k[7..-1]] = v
+        end
+      }
+      return kvargs
+    end
+
+    # Generate HTML form markup that contains the values in this
+    # message, to be HTTP POSTed as x-www-form-urlencoded UTF-8.
+    def to_form_markup(action_url, form_tag_attrs=nil, submit_text='Continue')
+      form_tag_attr_map = {}
+
+      if form_tag_attrs
+        form_tag_attrs.each { |name, attr|
+          form_tag_attr_map[name] = attr
+        }
+      end
+
+      form_tag_attr_map['action'] = action_url
+      form_tag_attr_map['method'] = 'post'
+      form_tag_attr_map['accept-charset'] = 'UTF-8'
+      form_tag_attr_map['enctype'] = 'application/x-www-form-urlencoded'
+
+      markup = "<form "
+
+      form_tag_attr_map.each { |k, v|
+        markup += " #{k}=\"#{v}\""
+      }
+
+      markup += ">\n"
+
+      to_post_args.each { |k,v|
+        markup += "<input type='hidden' name='#{k}' value='#{v}' />\n"
+      }
+      markup += "<input type='submit' value='#{submit_text}' />\n"
+      markup += "\n</form>"
+      return markup
+    end
+
+    # Generate a GET URL with the paramters in this message attacked as
+    # query parameters.
+    def to_url(base_url)
+      return Util.append_args(base_url, self.to_post_args)
+    end
+
+    # Generate a KVForm string that contains the parameters in this message.
+    # This will fail is the message contains arguments outside of the
+    # "openid." prefix.
+    def to_kvform
+      return Util.dict_to_kv(to_args)
+    end
+
+    # Generate an x-www-urlencoded string.
+    def to_url_encoded
+      args = to_post_args.map.sort
+      return Util.urlencode(args)
+    end
+
+    # Convert an input value into the internally used values of this obejct.
+    def _fix_ns(namespace)
+      if namespace == OPENID_NS
+        unless @openid_ns_uri
+          raise UndefinedOpenIDNamespace, 'OpenID namespace not set'
+        else
+          namespace = @openid_ns_uri
+        end
+      end
+
+      if namespace == BARE_NS
+        return namespace
+      end
+
+      if !namespace.is_a?(String)
+        raise ArgumentError, ("Namespace must be BARE_NS, OPENID_NS or "\
+                              "a string. Got #{namespace.inspect}")
+      end
+
+      if namespace.index(':').nil?
+        msg = ("OpenID 2.0 namespace identifiers SHOULD be URIs. "\
+               "Got #{namespace.inspect}")
+        Util.log(msg)
+
+        if namespace == 'sreg'
+          msg = "Using #{SREG_URI} instead of \"sreg\" as namespace"
+          Util.log(msg)
+          return SREG_URI
+        end
+      end
+
+      return namespace
+    end
+
+    def has_key?(namespace, ns_key)
+      namespace = _fix_ns(namespace)
+      return @args.member?([namespace, ns_key])
+    end
+
+    # Get the key for a particular namespaced argument
+    def get_key(namespace, ns_key)
+      namespace = _fix_ns(namespace)
+      return ns_key if namespace == BARE_NS
+
+      ns_alias = @namespaces.get_alias(namespace)
+
+      # no alias is defined, so no key can exist
+      return nil if ns_alias.nil?
+
+      if ns_alias == NULL_NAMESPACE
+        tail = ns_key
+      else
+        tail = "#{ns_alias}.#{ns_key}"
+      end
+
+      return 'openid.' + tail
+    end
+
+    # Get a value for a namespaced key.
+    def get_arg(namespace, key, default=nil)
+      namespace = _fix_ns(namespace)
+      @args.fetch([namespace, key]) {
+        if default == NO_DEFAULT
+          raise KeyNotFound, "<#{namespace}>#{key} not in this message"
+        else
+          default
+        end
+      }
+    end
+
+    # Get the arguments that are defined for this namespace URI.
+    def get_args(namespace)
+      namespace = _fix_ns(namespace)
+      args = {}
+      @args.each { |k,v|
+        pair_ns, ns_key = k
+        args[ns_key] = v if pair_ns == namespace
+      }
+      return args
+    end
+
+    # Set multiple key/value pairs in one call.
+    def update_args(namespace, updates)
+      namespace = _fix_ns(namespace)
+      updates.each {|k,v| set_arg(namespace, k, v)}
+    end
+
+    # Set a single argument in this namespace
+    def set_arg(namespace, key, value)
+      namespace = _fix_ns(namespace)
+      @args[[namespace, key].freeze] = value
+      if namespace != BARE_NS
+        @namespaces.add(namespace)
+      end
+    end
+
+    # Remove a single argument from this namespace.
+    def del_arg(namespace, key)
+      namespace = _fix_ns(namespace)
+      _key = [namespace, key]
+      @args.delete(_key)
+    end
+
+    def ==(other)
+      other.is_a?(self.class) && @args == other.instance_eval { @args }
+    end
+
+    def get_aliased_arg(aliased_key, default=nil)
+      if aliased_key == 'ns'
+        return get_openid_namespace()
+      end
+
+      ns_alias, key = aliased_key.split('.', 2)
+      if ns_alias == 'ns'
+        uri = @namespaces.get_namespace_uri(key)
+        if uri.nil? and default == NO_DEFAULT
+          raise KeyNotFound, "Namespace #{key} not defined when looking "\
+                             "for #{aliased_key}"
+        else
+          return (uri.nil? ? default : uri)
+        end
+      end
+
+      if key.nil?
+        key = aliased_key
+        ns = nil
+      else
+        ns = @namespaces.get_namespace_uri(ns_alias)
+      end
+
+      if ns.nil?
+        key = aliased_key
+        ns = get_openid_namespace
+      end
+
+      return get_arg(ns, key, default)
+    end
+  end
+
+
+  # Maintains a bidirectional map between namespace URIs and aliases.
+  class NamespaceMap
+
+    def initialize
+      @alias_to_namespace = {}
+      @namespace_to_alias = {}
+      @implicit_namespaces = []
+    end
+
+    def get_alias(namespace_uri)
+      @namespace_to_alias[namespace_uri]
+    end
+
+    def get_namespace_uri(namespace_alias)
+      @alias_to_namespace[namespace_alias]
+    end
+
+    # Add an alias from this namespace URI to the alias.
+    def add_alias(namespace_uri, desired_alias, implicit=false)
+      # Check that desired_alias is not an openid protocol field as
+      # per the spec.
+      Util.assert(!OPENID_PROTOCOL_FIELDS.include?(desired_alias),
+             "#{desired_alias} is not an allowed namespace alias")
+
+      # check that there is not a namespace already defined for the
+      # desired alias
+      current_namespace_uri = @alias_to_namespace.fetch(desired_alias, nil)
+      if current_namespace_uri and current_namespace_uri != namespace_uri
+        raise IndexError, "Cannot map #{namespace_uri} to alias #{desired_alias}. #{current_namespace_uri} is already mapped to alias #{desired_alias}"
+      end
+
+      # Check that desired_alias does not contain a period as per the
+      # spec.
+      if desired_alias.is_a?(String)
+          Util.assert(desired_alias.index('.').nil?,
+                 "#{desired_alias} must not contain a dot")
+      end
+
+      # check that there is not already a (different) alias for this
+      # namespace URI.
+      _alias = @namespace_to_alias[namespace_uri]
+      if _alias and _alias != desired_alias
+        raise IndexError, "Cannot map #{namespace_uri} to alias #{desired_alias}. It is already mapped to alias #{_alias}"
+      end
+
+      @alias_to_namespace[desired_alias] = namespace_uri
+      @namespace_to_alias[namespace_uri] = desired_alias
+      @implicit_namespaces << namespace_uri if implicit
+      return desired_alias
+    end
+
+    # Add this namespace URI to the mapping, without caring what alias
+    # it ends up with.
+    def add(namespace_uri)
+      # see if this namepace is already mapped to an alias
+      _alias = @namespace_to_alias[namespace_uri]
+      return _alias if _alias
+
+      # Fall back to generating a numberical alias
+      i = 0
+      while true
+        _alias = 'ext' + i.to_s
+        begin
+          add_alias(namespace_uri, _alias)
+        rescue IndexError
+          i += 1
+        else
+          return _alias
+        end
+      end
+
+      raise StandardError, 'Unreachable'
+    end
+
+    def member?(namespace_uri)
+      @namespace_to_alias.has_key?(namespace_uri)
+    end
+
+    def each
+      @namespace_to_alias.each {|k,v| yield k,v}
+    end
+
+    def namespace_uris
+      # Return an iterator over the namespace URIs
+      return @namespace_to_alias.keys()
+    end
+
+    def implicit?(namespace_uri)
+      return @implicit_namespaces.member?(namespace_uri)
+    end
+
+    def aliases
+      # Return an iterator over the aliases
+      return @alias_to_namespace.keys()
+    end
+  end
+end
diff --git a/vendor/gems/ruby-openid-2.1.4/lib/openid/protocolerror.rb b/vendor/gems/ruby-openid-2.1.4/lib/openid/protocolerror.rb
new file mode 100644 (file)
index 0000000..2aad0e4
--- /dev/null
@@ -0,0 +1,8 @@
+require 'openid/util'
+
+module OpenID
+
+  # An error in the OpenID protocol
+  class ProtocolError < OpenIDError
+  end
+end
diff --git a/vendor/gems/ruby-openid-2.1.4/lib/openid/server.rb b/vendor/gems/ruby-openid-2.1.4/lib/openid/server.rb
new file mode 100644 (file)
index 0000000..897b8bd
--- /dev/null
@@ -0,0 +1,1544 @@
+
+require 'openid/cryptutil'
+require 'openid/util'
+require 'openid/dh'
+require 'openid/store/nonce'
+require 'openid/trustroot'
+require 'openid/association'
+require 'openid/message'
+
+require 'time'
+
+module OpenID
+
+  module Server
+
+    HTTP_OK = 200
+    HTTP_REDIRECT = 302
+    HTTP_ERROR = 400
+
+    BROWSER_REQUEST_MODES = ['checkid_setup', 'checkid_immediate']
+
+    ENCODE_KVFORM = ['kvform'].freeze
+    ENCODE_URL = ['URL/redirect'].freeze
+    ENCODE_HTML_FORM = ['HTML form'].freeze
+
+    UNUSED = nil
+
+    class OpenIDRequest
+      attr_accessor :message, :mode
+
+      # I represent an incoming OpenID request.
+      #
+      # Attributes:
+      # mode:: The "openid.mode" of this request
+      def initialize
+        @mode = nil
+        @message = nil
+      end
+
+      def namespace
+        if @message.nil?
+          raise RuntimeError, "Request has no message"
+        else
+          return @message.get_openid_namespace
+        end
+      end
+    end
+
+    # A request to verify the validity of a previous response.
+    #
+    # See OpenID Specs, Verifying Directly with the OpenID Provider
+    # <http://openid.net/specs/openid-authentication-2_0-12.html#verifying_signatures>
+    class CheckAuthRequest < OpenIDRequest
+
+      # The association handle the response was signed with.
+      attr_accessor :assoc_handle
+
+      # The message with the signature which wants checking.
+      attr_accessor :signed
+
+      # An association handle the client is asking about the validity
+      # of. May be nil.
+      attr_accessor :invalidate_handle
+
+      attr_accessor :sig
+
+      # Construct me.
+      #
+      # These parameters are assigned directly as class attributes.
+      #
+      # Parameters:
+      # assoc_handle:: the association handle for this request
+      # signed:: The signed message
+      # invalidate_handle:: An association handle that the relying
+      #                     party is checking to see if it is invalid
+      def initialize(assoc_handle, signed, invalidate_handle=nil)
+        super()
+
+        @mode = "check_authentication"
+        @required_fields = ["identity", "return_to", "response_nonce"].freeze
+
+        @sig = nil
+        @assoc_handle = assoc_handle
+        @signed = signed
+        @invalidate_handle = invalidate_handle
+      end
+
+      # Construct me from an OpenID::Message.
+      def self.from_message(message, op_endpoint=UNUSED)
+        assoc_handle = message.get_arg(OPENID_NS, 'assoc_handle')
+        invalidate_handle = message.get_arg(OPENID_NS, 'invalidate_handle')
+
+        signed = message.copy()
+        # openid.mode is currently check_authentication because
+        # that's the mode of this request.  But the signature
+        # was made on something with a different openid.mode.
+        # http://article.gmane.org/gmane.comp.web.openid.general/537
+        if signed.has_key?(OPENID_NS, "mode")
+          signed.set_arg(OPENID_NS, "mode", "id_res")
+        end
+
+        obj = self.new(assoc_handle, signed, invalidate_handle)
+        obj.message = message
+        obj.sig = message.get_arg(OPENID_NS, 'sig')
+
+        if !obj.assoc_handle or
+            !obj.sig
+          msg = sprintf("%s request missing required parameter from message %s",
+                        obj.mode, message)
+            raise ProtocolError.new(message, msg)
+        end
+
+        return obj
+      end
+
+      # Respond to this request.
+      #
+      # Given a Signatory, I can check the validity of the signature
+      # and the invalidate_handle.  I return a response with an
+      # is_valid (and, if appropriate invalidate_handle) field.
+      def answer(signatory)
+        is_valid = signatory.verify(@assoc_handle, @signed)
+        # Now invalidate that assoc_handle so it this checkAuth
+        # message cannot be replayed.
+        signatory.invalidate(@assoc_handle, dumb=true)
+        response = OpenIDResponse.new(self)
+        valid_str = is_valid ? "true" : "false"
+        response.fields.set_arg(OPENID_NS, 'is_valid', valid_str)
+
+        if @invalidate_handle
+          assoc = signatory.get_association(@invalidate_handle, false)
+          if !assoc
+            response.fields.set_arg(
+                    OPENID_NS, 'invalidate_handle', @invalidate_handle)
+          end
+        end
+
+        return response
+      end
+
+      def to_s
+        ih = nil
+
+        if @invalidate_handle
+          ih = sprintf(" invalidate? %s", @invalidate_handle)
+        else
+          ih = ""
+        end
+
+        s = sprintf("<%s handle: %s sig: %s: signed: %s%s>",
+                    self.class, @assoc_handle,
+                    @sig, @signed, ih)
+        return s
+      end
+    end
+
+    class BaseServerSession
+      attr_reader :session_type
+
+      def initialize(session_type, allowed_assoc_types)
+        @session_type = session_type
+        @allowed_assoc_types = allowed_assoc_types.dup.freeze
+      end
+
+      def allowed_assoc_type?(typ)
+        @allowed_assoc_types.member?(typ)
+      end
+    end
+
+    # An object that knows how to handle association requests with
+    # no session type.
+    #
+    # See OpenID Specs, Section 8: Establishing Associations
+    # <http://openid.net/specs/openid-authentication-2_0-12.html#associations>
+    class PlainTextServerSession < BaseServerSession
+      # The session_type for this association session. There is no
+      # type defined for plain-text in the OpenID specification, so we
+      # use 'no-encryption'.
+      attr_reader :session_type
+
+      def initialize
+        super('no-encryption', ['HMAC-SHA1', 'HMAC-SHA256'])
+      end
+
+      def self.from_message(unused_request)
+        return self.new
+      end
+
+      def answer(secret)
+        return {'mac_key' => Util.to_base64(secret)}
+      end
+    end
+
+    # An object that knows how to handle association requests with the
+    # Diffie-Hellman session type.
+    #
+    # See OpenID Specs, Section 8: Establishing Associations
+    # <http://openid.net/specs/openid-authentication-2_0-12.html#associations>
+    class DiffieHellmanSHA1ServerSession < BaseServerSession
+
+      # The Diffie-Hellman algorithm values for this request
+      attr_accessor :dh
+
+      # The public key sent by the consumer in the associate request
+      attr_accessor :consumer_pubkey
+
+      # The session_type for this association session.
+      attr_reader :session_type
+
+      def initialize(dh, consumer_pubkey)
+        super('DH-SHA1', ['HMAC-SHA1'])
+
+        @hash_func = CryptUtil.method('sha1')
+        @dh = dh
+        @consumer_pubkey = consumer_pubkey
+      end
+
+      # Construct me from OpenID Message
+      #
+      # Raises ProtocolError when parameters required to establish the
+      # session are missing.
+      def self.from_message(message)
+        dh_modulus = message.get_arg(OPENID_NS, 'dh_modulus')
+        dh_gen = message.get_arg(OPENID_NS, 'dh_gen')
+        if ((!dh_modulus and dh_gen) or
+            (!dh_gen and dh_modulus))
+
+          if !dh_modulus
+            missing = 'modulus'
+          else
+            missing = 'generator'
+          end
+
+          raise ProtocolError.new(message,
+                  sprintf('If non-default modulus or generator is ' +
+                          'supplied, both must be supplied. Missing %s',
+                          missing))
+        end
+
+        if dh_modulus or dh_gen
+          dh_modulus = CryptUtil.base64_to_num(dh_modulus)
+          dh_gen = CryptUtil.base64_to_num(dh_gen)
+          dh = DiffieHellman.new(dh_modulus, dh_gen)
+        else
+          dh = DiffieHellman.from_defaults()
+        end
+
+        consumer_pubkey = message.get_arg(OPENID_NS, 'dh_consumer_public')
+        if !consumer_pubkey
+          raise ProtocolError.new(message,
+                  sprintf("Public key for DH-SHA1 session " +
+                          "not found in message %s", message))
+        end
+
+        consumer_pubkey = CryptUtil.base64_to_num(consumer_pubkey)
+
+        return self.new(dh, consumer_pubkey)
+      end
+
+      def answer(secret)
+        mac_key = @dh.xor_secret(@hash_func,
+                                 @consumer_pubkey,
+                                 secret)
+        return {
+            'dh_server_public' => CryptUtil.num_to_base64(@dh.public),
+            'enc_mac_key' => Util.to_base64(mac_key),
+            }
+      end
+    end
+
+    class DiffieHellmanSHA256ServerSession < DiffieHellmanSHA1ServerSession
+      def initialize(*args)
+        super(*args)
+        @session_type = 'DH-SHA256'
+        @hash_func = CryptUtil.method('sha256')
+        @allowed_assoc_types = ['HMAC-SHA256'].freeze
+      end
+    end
+
+    # A request to establish an association.
+    #
+    # See OpenID Specs, Section 8: Establishing Associations
+    # <http://openid.net/specs/openid-authentication-2_0-12.html#associations>
+    class AssociateRequest < OpenIDRequest
+      # An object that knows how to handle association requests of a
+      # certain type.
+      attr_accessor :session
+
+      # The type of association. Supported values include HMAC-SHA256
+      # and HMAC-SHA1
+      attr_accessor :assoc_type
+
+      @@session_classes = {
+        'no-encryption' => PlainTextServerSession,
+        'DH-SHA1' => DiffieHellmanSHA1ServerSession,
+        'DH-SHA256' => DiffieHellmanSHA256ServerSession,
+      }
+
+      # Construct me.
+      #
+      # The session is assigned directly as a class attribute. See my
+      # class documentation for its description.
+      def initialize(session, assoc_type)
+        super()
+        @session = session
+        @assoc_type = assoc_type
+
+        @mode = "associate"
+      end
+
+      # Construct me from an OpenID Message.
+      def self.from_message(message, op_endpoint=UNUSED)
+        if message.is_openid1()
+          session_type = message.get_arg(OPENID_NS, 'session_type')
+          if session_type == 'no-encryption'
+            Util.log('Received OpenID 1 request with a no-encryption ' +
+                     'association session type. Continuing anyway.')
+          elsif !session_type
+            session_type = 'no-encryption'
+          end
+        else
+          session_type = message.get_arg(OPENID2_NS, 'session_type')
+          if !session_type
+            raise ProtocolError.new(message,
+                                    text="session_type missing from request")
+          end
+        end
+
+        session_class = @@session_classes[session_type]
+
+        if !session_class
+          raise ProtocolError.new(message,
+                  sprintf("Unknown session type %s", session_type))
+        end
+
+        begin
+          session = session_class.from_message(message)
+        rescue ArgumentError => why
+          # XXX
+          raise ProtocolError.new(message,
+                                  sprintf('Error parsing %s session: %s',
+                                          session_type, why))
+        end
+
+        assoc_type = message.get_arg(OPENID_NS, 'assoc_type', 'HMAC-SHA1')
+        if !session.allowed_assoc_type?(assoc_type)
+          msg = sprintf('Session type %s does not support association type %s',
+                        session_type, assoc_type)
+          raise ProtocolError.new(message, msg)
+        end
+
+        obj = self.new(session, assoc_type)
+        obj.message = message
+        return obj
+      end
+
+      # Respond to this request with an association.
+      #
+      # assoc:: The association to send back.
+      #
+      # Returns a response with the association information, encrypted
+      # to the consumer's public key if appropriate.
+      def answer(assoc)
+        response = OpenIDResponse.new(self)
+        response.fields.update_args(OPENID_NS, {
+            'expires_in' => sprintf('%d', assoc.expires_in()),
+            'assoc_type' => @assoc_type,
+            'assoc_handle' => assoc.handle,
+            })
+        response.fields.update_args(OPENID_NS,
+                                   @session.answer(assoc.secret))
+        unless (@session.session_type == 'no-encryption' and
+                @message.is_openid1)
+          response.fields.set_arg(
+              OPENID_NS, 'session_type', @session.session_type)
+        end
+
+        return response
+      end
+
+      # Respond to this request indicating that the association type
+      # or association session type is not supported.
+      def answer_unsupported(message, preferred_association_type=nil,
+                             preferred_session_type=nil)
+        if @message.is_openid1()
+          raise ProtocolError.new(@message)
+        end
+
+        response = OpenIDResponse.new(self)
+        response.fields.set_arg(OPENID_NS, 'error_code', 'unsupported-type')
+        response.fields.set_arg(OPENID_NS, 'error', message)
+
+        if preferred_association_type
+          response.fields.set_arg(
+              OPENID_NS, 'assoc_type', preferred_association_type)
+        end
+
+        if preferred_session_type
+          response.fields.set_arg(
+              OPENID_NS, 'session_type', preferred_session_type)
+        end
+
+        return response
+      end
+    end
+
+    # A request to confirm the identity of a user.
+    #
+    # This class handles requests for openid modes
+    # +checkid_immediate+ and +checkid_setup+ .
+    class CheckIDRequest < OpenIDRequest
+
+      # Provided in smart mode requests, a handle for a previously
+      # established association.  nil for dumb mode requests.
+      attr_accessor :assoc_handle
+
+      # Is this an immediate-mode request?
+      attr_accessor :immediate
+
+      # The URL to send the user agent back to to reply to this
+      # request.
+      attr_accessor :return_to
+
+      # The OP-local identifier being checked.
+      attr_accessor :identity
+
+      # The claimed identifier.  Not present in OpenID 1.x
+      # messages.
+      attr_accessor :claimed_id
+
+      # This URL identifies the party making the request, and the user
+      # will use that to make her decision about what answer she
+      # trusts them to have. Referred to as "realm" in OpenID 2.0.
+      attr_accessor :trust_root
+
+      # mode:: +checkid_immediate+ or +checkid_setup+
+      attr_accessor :mode
+
+      attr_accessor :op_endpoint
+
+      # These parameters are assigned directly as attributes,
+      # see the #CheckIDRequest class documentation for their
+      # descriptions.
+      #
+      # Raises #MalformedReturnURL when the +return_to+ URL is not
+      # a URL.
+      def initialize(identity, return_to, op_endpoint, trust_root=nil,
+                     immediate=false, assoc_handle=nil, claimed_id=nil)
+        @assoc_handle = assoc_handle
+        @identity = identity
+        @claimed_id = (claimed_id or identity)
+        @return_to = return_to
+        @trust_root = (trust_root or return_to)
+        @op_endpoint = op_endpoint
+        @message = nil
+
+        if immediate
+          @immediate = true
+          @mode = "checkid_immediate"
+        else
+          @immediate = false
+          @mode = "checkid_setup"
+        end
+
+        if @return_to and
+            !TrustRoot::TrustRoot.parse(@return_to)
+          raise MalformedReturnURL.new(nil, @return_to)
+        end
+
+        if !trust_root_valid()
+          raise UntrustedReturnURL.new(nil, @return_to, @trust_root)
+        end
+      end
+
+      # Construct me from an OpenID message.
+      #
+      # message:: An OpenID checkid_* request Message
+      #
+      # op_endpoint:: The endpoint URL of the server that this
+      #               message was sent to.
+      #
+      # Raises:
+      # ProtocolError:: When not all required parameters are present
+      #                 in the message.
+      #
+      # MalformedReturnURL:: When the +return_to+ URL is not a URL.
+      #
+      # UntrustedReturnURL:: When the +return_to+ URL is
+      #                      outside the +trust_root+.
+      def self.from_message(message, op_endpoint)
+        obj = self.allocate
+        obj.message = message
+        obj.op_endpoint = op_endpoint
+        mode = message.get_arg(OPENID_NS, 'mode')
+        if mode == "checkid_immediate"
+          obj.immediate = true
+          obj.mode = "checkid_immediate"
+        else
+          obj.immediate = false
+          obj.mode = "checkid_setup"
+        end
+
+        obj.return_to = message.get_arg(OPENID_NS, 'return_to')
+        if message.is_openid1 and !obj.return_to
+          msg = sprintf("Missing required field 'return_to' from %s",
+                        message)
+          raise ProtocolError.new(message, msg)
+        end
+
+        obj.identity = message.get_arg(OPENID_NS, 'identity')
+        obj.claimed_id = message.get_arg(OPENID_NS, 'claimed_id')
+        if message.is_openid1()
+          if !obj.identity
+            s = "OpenID 1 message did not contain openid.identity"
+            raise ProtocolError.new(message, s)
+          end
+        else
+          if obj.identity and not obj.claimed_id
+            s = ("OpenID 2.0 message contained openid.identity but not " +
+                 "claimed_id")
+            raise ProtocolError.new(message, s)
+          elsif obj.claimed_id and not obj.identity
+            s = ("OpenID 2.0 message contained openid.claimed_id but not " +
+                 "identity")
+            raise ProtocolError.new(message, s)
+          end
+        end
+
+        # There's a case for making self.trust_root be a TrustRoot
+        # here.  But if TrustRoot isn't currently part of the "public"
+        # API, I'm not sure it's worth doing.
+        if message.is_openid1
+          trust_root_param = 'trust_root'
+        else
+          trust_root_param = 'realm'
+        end
+        trust_root = message.get_arg(OPENID_NS, trust_root_param)
+        trust_root = obj.return_to if (trust_root.nil? || trust_root.empty?)
+        obj.trust_root = trust_root
+
+        if !message.is_openid1 and !obj.return_to and !obj.trust_root
+          raise ProtocolError.new(message, "openid.realm required when " +
+                                  "openid.return_to absent")
+        end
+
+        obj.assoc_handle = message.get_arg(OPENID_NS, 'assoc_handle')
+
+        # Using TrustRoot.parse here is a bit misleading, as we're not
+        # parsing return_to as a trust root at all.  However, valid
+        # URLs are valid trust roots, so we can use this to get an
+        # idea if it is a valid URL.  Not all trust roots are valid
+        # return_to URLs, however (particularly ones with wildcards),
+        # so this is still a little sketchy.
+        if obj.return_to and \
+          !TrustRoot::TrustRoot.parse(obj.return_to)
+          raise MalformedReturnURL.new(message, obj.return_to)
+        end
+
+        # I first thought that checking to see if the return_to is
+        # within the trust_root is premature here, a
+        # logic-not-decoding thing.  But it was argued that this is
+        # really part of data validation.  A request with an invalid
+        # trust_root/return_to is broken regardless of application,
+        # right?
+        if !obj.trust_root_valid()
+          raise UntrustedReturnURL.new(message, obj.return_to, obj.trust_root)
+        end
+
+        return obj
+      end
+
+      # Is the identifier to be selected by the IDP?
+      def id_select
+        # So IDPs don't have to import the constant
+        return @identity == IDENTIFIER_SELECT
+      end
+
+      # Is my return_to under my trust_root?
+      def trust_root_valid
+        if !@trust_root
+          return true
+        end
+
+        tr = TrustRoot::TrustRoot.parse(@trust_root)
+        if !tr
+          raise MalformedTrustRoot.new(@message, @trust_root)
+        end
+
+        if @return_to
+          return tr.validate_url(@return_to)
+        else
+          return true
+        end
+      end
+
+      # Does the relying party publish the return_to URL for this
+      # response under the realm? It is up to the provider to set a
+      # policy for what kinds of realms should be allowed. This
+      # return_to URL verification reduces vulnerability to
+      # data-theft attacks based on open proxies,
+      # corss-site-scripting, or open redirectors.
+      #
+      # This check should only be performed after making sure that
+      # the return_to URL matches the realm.
+      #
+      # Raises DiscoveryFailure if the realm
+      # URL does not support Yadis discovery (and so does not
+      # support the verification process).
+      #
+      # Returns true if the realm publishes a document with the
+      # return_to URL listed
+      def return_to_verified
+        return TrustRoot.verify_return_to(@trust_root, @return_to)
+      end
+
+      # Respond to this request.
+      #
+      # allow:: Allow this user to claim this identity, and allow the
+      #         consumer to have this information?
+      #
+      # server_url:: DEPRECATED.  Passing op_endpoint to the
+      #              #Server constructor makes this optional.
+      #
+      #              When an OpenID 1.x immediate mode request does
+      #              not succeed, it gets back a URL where the request
+      #              may be carried out in a not-so-immediate fashion.
+      #              Pass my URL in here (the fully qualified address
+      #              of this server's endpoint, i.e.
+      #              <tt>http://example.com/server</tt>), and I will
+      #              use it as a base for the URL for a new request.
+      #
+      #              Optional for requests where
+      #              #CheckIDRequest.immediate is false or +allow+ is
+      #              true.
+      #
+      # identity:: The OP-local identifier to answer with.  Only for use
+      #            when the relying party requested identifier selection.
+      #
+      # claimed_id:: The claimed identifier to answer with,
+      #              for use with identifier selection in the case where the
+      #              claimed identifier and the OP-local identifier differ,
+      #              i.e. when the claimed_id uses delegation.
+      #
+      #              If +identity+ is provided but this is not,
+      #              +claimed_id+ will default to the value of +identity+.
+      #              When answering requests that did not ask for identifier
+      #              selection, the response +claimed_id+ will default to
+      #              that of the request.
+      #
+      #              This parameter is new in OpenID 2.0.
+      #
+      # Returns an OpenIDResponse object containing a OpenID id_res message.
+      #
+      # Raises NoReturnToError if the return_to is missing.
+      #
+      # Version 2.0 deprecates +server_url+ and adds +claimed_id+.
+      def answer(allow, server_url=nil, identity=nil, claimed_id=nil)
+        if !@return_to
+          raise NoReturnToError
+        end
+
+        if !server_url
+          if @message.is_openid2 and !@op_endpoint
+            # In other words, that warning I raised in
+            # Server.__init__?  You should pay attention to it now.
+            raise RuntimeError, ("#{self} should be constructed with "\
+                                 "op_endpoint to respond to OpenID 2.0 "\
+                                 "messages.")
+          end
+
+          server_url = @op_endpoint
+        end
+
+        if allow
+          mode = 'id_res'
+        elsif @message.is_openid1
+          if @immediate
+            mode = 'id_res'
+          else
+            mode = 'cancel'
+          end
+        else
+          if @immediate
+            mode = 'setup_needed'
+          else
+            mode = 'cancel'
+          end
+        end
+
+        response = OpenIDResponse.new(self)
+
+        if claimed_id and @message.is_openid1
+          raise VersionError, ("claimed_id is new in OpenID 2.0 and not "\
+                               "available for #{@message.get_openid_namespace}")
+        end
+
+        if identity and !claimed_id
+          claimed_id = identity
+        end
+
+        if allow
+          if @identity == IDENTIFIER_SELECT
+            if !identity
+              raise ArgumentError, ("This request uses IdP-driven "\
+                                    "identifier selection.You must supply "\
+                                    "an identifier in the response.")
+            end
+
+            response_identity = identity
+            response_claimed_id = claimed_id
+
+          elsif @identity
+            if identity and (@identity != identity)
+              raise ArgumentError, ("Request was for identity #{@identity}, "\
+                                    "cannot reply with identity #{identity}")
+            end
+
+            response_identity = @identity
+            response_claimed_id = @claimed_id
+          else
+            if identity
+              raise ArgumentError, ("This request specified no identity "\
+                                    "and you supplied #{identity}")
+            end
+            response_identity = nil
+          end
+
+          if @message.is_openid1 and !response_identity
+            raise ArgumentError, ("Request was an OpenID 1 request, so "\
+                                  "response must include an identifier.")
+          end
+
+          response.fields.update_args(OPENID_NS, {
+                'mode' => mode,
+                'op_endpoint' => server_url,
+                'return_to' => @return_to,
+                'response_nonce' => Nonce.mk_nonce(),
+                })
+
+          if response_identity
+            response.fields.set_arg(OPENID_NS, 'identity', response_identity)
+            if @message.is_openid2
+              response.fields.set_arg(OPENID_NS,
+                                      'claimed_id', response_claimed_id)
+            end
+          end
+        else
+          response.fields.set_arg(OPENID_NS, 'mode', mode)
+          if @immediate
+            if @message.is_openid1 and !server_url
+              raise ArgumentError, ("setup_url is required for allow=false "\
+                                    "in OpenID 1.x immediate mode.")
+            end
+
+            # Make a new request just like me, but with
+            # immediate=false.
+            setup_request = self.class.new(@identity, @return_to,
+                                           @op_endpoint, @trust_root, false,
+                                           @assoc_handle, @claimed_id)
+            setup_request.message = Message.new(@message.get_openid_namespace)
+            setup_url = setup_request.encode_to_url(server_url)
+            response.fields.set_arg(OPENID_NS, 'user_setup_url', setup_url)
+          end
+        end
+
+        return response
+      end
+
+      def encode_to_url(server_url)
+        # Encode this request as a URL to GET.
+        #
+        # server_url:: The URL of the OpenID server to make this
+        #              request of.
+        if !@return_to
+          raise NoReturnToError
+        end
+
+        # Imported from the alternate reality where these classes are
+        # used in both the client and server code, so Requests are
+        # Encodable too.  That's right, code imported from alternate
+        # realities all for the love of you, id_res/user_setup_url.
+        q = {'mode' => @mode,
+             'identity' => @identity,
+             'claimed_id' => @claimed_id,
+             'return_to' => @return_to}
+
+        if @trust_root
+          if @message.is_openid1
+            q['trust_root'] = @trust_root
+          else
+            q['realm'] = @trust_root
+          end
+        end
+
+        if @assoc_handle
+          q['assoc_handle'] = @assoc_handle
+        end
+
+        response = Message.new(@message.get_openid_namespace)
+        response.update_args(@message.get_openid_namespace, q)
+        return response.to_url(server_url)
+      end
+
+      def cancel_url
+        # Get the URL to cancel this request.
+        #
+        # Useful for creating a "Cancel" button on a web form so that
+        # operation can be carried out directly without another trip
+        # through the server.
+        #
+        # (Except you may want to make another trip through the
+        # server so that it knows that the user did make a decision.)
+        #
+        # Returns a URL as a string.
+        if !@return_to
+          raise NoReturnToError
+        end
+
+        if @immediate
+          raise ArgumentError.new("Cancel is not an appropriate response to " +
+                                  "immediate mode requests.")
+        end
+
+        response = Message.new(@message.get_openid_namespace)
+        response.set_arg(OPENID_NS, 'mode', 'cancel')
+        return response.to_url(@return_to)
+      end
+
+      def to_s
+        return sprintf('<%s id:%s im:%s tr:%s ah:%s>', self.class,
+                       @identity,
+                       @immediate,
+                       @trust_root,
+                       @assoc_handle)
+      end
+    end
+
+    # I am a response to an OpenID request.
+    #
+    # Attributes:
+    # signed:: A list of the names of the fields which should be signed.
+    #
+    # Implementer's note: In a more symmetric client/server
+    # implementation, there would be more types of #OpenIDResponse
+    # object and they would have validated attributes according to
+    # the type of response.  But as it is, Response objects in a
+    # server are basically write-only, their only job is to go out
+    # over the wire, so this is just a loose wrapper around
+    # #OpenIDResponse.fields.
+    class OpenIDResponse
+      # The #OpenIDRequest I respond to.
+      attr_accessor :request
+
+      # An #OpenID::Message with the data to be returned.
+      # Keys are parameter names with no
+      # leading openid. e.g. identity and mac_key
+      # never openid.identity.
+      attr_accessor :fields
+
+      def initialize(request)
+        # Make a response to an OpenIDRequest.
+        @request = request
+        @fields = Message.new(request.namespace)
+      end
+
+      def to_s
+        return sprintf("%s for %s: %s",
+                       self.class,
+                       @request.class,
+                       @fields)
+      end
+
+      # form_tag_attrs is a hash of attributes to be added to the form
+      # tag. 'accept-charset' and 'enctype' have defaults that can be
+      # overridden. If a value is supplied for 'action' or 'method',
+      # it will be replaced.       
+      # Returns the form markup for this response.
+      def to_form_markup(form_tag_attrs=nil)
+        return @fields.to_form_markup(@request.return_to, form_tag_attrs)
+      end
+
+      # Wraps the form tag from to_form_markup in a complete HTML document
+      # that uses javascript to autosubmit the form.
+      def to_html(form_tag_attrs=nil)
+        return Util.auto_submit_html(to_form_markup(form_tag_attrs))
+      end
+
+      def render_as_form
+        # Returns true if this response's encoding is
+        # ENCODE_HTML_FORM.  Convenience method for server authors.
+        return self.which_encoding == ENCODE_HTML_FORM
+      end
+
+      def needs_signing
+        # Does this response require signing?
+        return @fields.get_arg(OPENID_NS, 'mode') == 'id_res'
+      end
+
+      # implements IEncodable
+
+      def which_encoding
+        # How should I be encoded?
+        # returns one of ENCODE_URL or ENCODE_KVFORM.
+        if BROWSER_REQUEST_MODES.member?(@request.mode)
+          if @fields.is_openid2 and
+              encode_to_url.length > OPENID1_URL_LIMIT
+            return ENCODE_HTML_FORM
+          else
+            return ENCODE_URL
+          end
+        else
+          return ENCODE_KVFORM
+        end
+      end
+
+      def encode_to_url
+        # Encode a response as a URL for the user agent to GET.
+        # You will generally use this URL with a HTTP redirect.
+        return @fields.to_url(@request.return_to)
+      end
+
+      def add_extension(extension_response)
+        # Add an extension response to this response message.
+        #
+        # extension_response:: An object that implements the
+        #     #OpenID::Extension interface for adding arguments to an OpenID
+        #     message.
+        extension_response.to_message(@fields)
+      end
+
+      def encode_to_kvform
+        # Encode a response in key-value colon/newline format.
+        #
+        # This is a machine-readable format used to respond to
+        # messages which came directly from the consumer and not
+        # through the user agent.
+        #
+        # see: OpenID Specs,
+        #    <a href="http://openid.net/specs.bml#keyvalue">Key-Value Colon/Newline format</a>
+        return @fields.to_kvform
+      end
+
+      def copy
+        return Marshal.load(Marshal.dump(self))
+      end
+    end
+
+    # I am a response to an OpenID request in terms a web server
+    # understands.
+    #
+    # I generally come from an #Encoder, either directly or from
+    # #Server.encodeResponse.
+    class WebResponse
+
+      # The HTTP code of this response as an integer.
+      attr_accessor :code
+
+      # #Hash of headers to include in this response.
+      attr_accessor :headers
+
+      # The body of this response.
+      attr_accessor :body
+
+      def initialize(code=HTTP_OK, headers=nil, body="")
+        # Construct me.
+        #
+        # These parameters are assigned directly as class attributes,
+        # see my class documentation for their
+        # descriptions.
+        @code = code
+        if headers
+          @headers = headers
+        else
+          @headers = {}
+        end
+        @body = body
+      end
+    end
+
+    # I sign things.
+    #
+    # I also check signatures.
+    #
+    # All my state is encapsulated in a store, which means I'm not
+    # generally pickleable but I am easy to reconstruct.
+    class Signatory
+      # The number of seconds a secret remains valid. Defaults to 14 days.
+      attr_accessor :secret_lifetime
+
+      # keys have a bogus server URL in them because the filestore
+      # really does expect that key to be a URL.  This seems a little
+      # silly for the server store, since I expect there to be only
+      # one server URL.
+      @@_normal_key = 'http://localhost/|normal'
+      @@_dumb_key = 'http://localhost/|dumb'
+
+      def self._normal_key
+        @@_normal_key
+      end
+
+      def self._dumb_key
+        @@_dumb_key
+      end
+
+      attr_accessor :store
+
+      # Create a new Signatory. store is The back-end where my
+      # associations are stored.
+      def initialize(store)
+        Util.assert(store)
+        @store = store
+        @secret_lifetime = 14 * 24 * 60 * 60
+      end
+
+      # Verify that the signature for some data is valid.
+      def verify(assoc_handle, message)
+        assoc = get_association(assoc_handle, true)
+        if !assoc
+          Util.log(sprintf("failed to get assoc with handle %s to verify " +
+                           "message %s", assoc_handle, message))
+          return false
+        end
+
+        begin
+          valid = assoc.check_message_signature(message)
+        rescue StandardError => ex
+          Util.log(sprintf("Error in verifying %s with %s: %s",
+                           message, assoc, ex))
+          return false
+        end
+
+        return valid
+      end
+
+      # Sign a response.
+      #
+      # I take an OpenIDResponse, create a signature for everything in
+      # its signed list, and return a new copy of the response object
+      # with that signature included.
+      def sign(response)
+        signed_response = response.copy
+        assoc_handle = response.request.assoc_handle
+        if assoc_handle
+          # normal mode disabling expiration check because even if the
+          # association is expired, we still need to know some
+          # properties of the association so that we may preserve
+          # those properties when creating the fallback association.
+          assoc = get_association(assoc_handle, false, false)
+
+          if !assoc or assoc.expires_in <= 0
+            # fall back to dumb mode
+            signed_response.fields.set_arg(
+                  OPENID_NS, 'invalidate_handle', assoc_handle)
+            assoc_type = assoc ? assoc.assoc_type : 'HMAC-SHA1'
+            if assoc and assoc.expires_in <= 0
+              # now do the clean-up that the disabled checkExpiration
+              # code didn't get to do.
+              invalidate(assoc_handle, false)
+            end
+            assoc = create_association(true, assoc_type)
+          end
+        else
+          # dumb mode.
+          assoc = create_association(true)
+        end
+
+        begin
+          signed_response.fields = assoc.sign_message(signed_response.fields)
+        rescue KVFormError => err
+          raise EncodingError, err
+        end
+        return signed_response
+      end
+
+      # Make a new association.
+      def create_association(dumb=true, assoc_type='HMAC-SHA1')
+        secret = CryptUtil.random_string(OpenID.get_secret_size(assoc_type))
+        uniq = Util.to_base64(CryptUtil.random_string(4))
+        handle = sprintf('{%s}{%x}{%s}', assoc_type, Time.now.to_i, uniq)
+
+        assoc = Association.from_expires_in(
+            secret_lifetime, handle, secret, assoc_type)
+
+        if dumb
+          key = @@_dumb_key
+        else
+          key = @@_normal_key
+        end
+
+        @store.store_association(key, assoc)
+        return assoc
+      end
+
+      # Get the association with the specified handle.
+      def get_association(assoc_handle, dumb, checkExpiration=true)
+        # Hmm.  We've created an interface that deals almost entirely
+        # with assoc_handles.  The only place outside the Signatory
+        # that uses this (and thus the only place that ever sees
+        # Association objects) is when creating a response to an
+        # association request, as it must have the association's
+        # secret.
+
+        if !assoc_handle
+          raise ArgumentError.new("assoc_handle must not be None")
+        end
+
+        if dumb
+          key = @@_dumb_key
+        else
+          key = @@_normal_key
+        end
+
+        assoc = @store.get_association(key, assoc_handle)
+        if assoc and assoc.expires_in <= 0
+          Util.log(sprintf("requested %sdumb key %s is expired (by %s seconds)",
+                           (!dumb) ? 'not-' : '',
+                           assoc_handle, assoc.expires_in))
+          if checkExpiration
+            @store.remove_association(key, assoc_handle)
+            assoc = nil
+          end
+        end
+
+        return assoc
+      end
+
+      # Invalidates the association with the given handle.
+      def invalidate(assoc_handle, dumb)
+        if dumb
+          key = @@_dumb_key
+        else
+          key = @@_normal_key
+        end
+
+        @store.remove_association(key, assoc_handle)
+      end
+    end
+
+    # I encode responses in to WebResponses.
+    #
+    # If you don't like WebResponses, you can do
+    # your own handling of OpenIDResponses with
+    # OpenIDResponse.whichEncoding,
+    # OpenIDResponse.encodeToURL, and
+    # OpenIDResponse.encodeToKVForm.
+    class Encoder
+      @@responseFactory = WebResponse
+
+      # Encode a response to a WebResponse.
+      #
+      # Raises EncodingError when I can't figure out how to encode
+      # this message.
+      def encode(response)
+        encode_as = response.which_encoding()
+        if encode_as == ENCODE_KVFORM
+          wr = @@responseFactory.new(HTTP_OK, nil,
+                                     response.encode_to_kvform())
+          if response.is_a?(Exception)
+            wr.code = HTTP_ERROR
+          end
+        elsif encode_as == ENCODE_URL
+          location = response.encode_to_url()
+          wr = @@responseFactory.new(HTTP_REDIRECT,
+                                     {'location' => location})
+        elsif encode_as == ENCODE_HTML_FORM
+          wr = @@responseFactory.new(HTTP_OK, nil,
+                                     response.to_form_markup())
+        else
+          # Can't encode this to a protocol message.  You should
+          # probably render it to HTML and show it to the user.
+          raise EncodingError.new(response)
+        end
+
+        return wr
+      end
+    end
+
+    # I encode responses in to WebResponses, signing
+    # them when required.
+    class SigningEncoder < Encoder
+
+      attr_accessor :signatory
+
+      # Create a SigningEncoder given a Signatory
+      def initialize(signatory)
+        @signatory = signatory
+      end
+
+      # Encode a response to a WebResponse, signing it first if
+      # appropriate.
+      #
+      # Raises EncodingError when I can't figure out how to encode this
+      # message.
+      #
+      # Raises AlreadySigned when this response is already signed.
+      def encode(response)
+        # the is_a? is a bit of a kludge... it means there isn't
+        # really an adapter to make the interfaces quite match.
+        if !response.is_a?(Exception) and response.needs_signing()
+          if !@signatory
+            raise ArgumentError.new(
+              sprintf("Must have a store to sign this request: %s",
+                      response), response)
+          end
+
+          if response.fields.has_key?(OPENID_NS, 'sig')
+            raise AlreadySigned.new(response)
+          end
+
+          response = @signatory.sign(response)
+        end
+
+        return super(response)
+      end
+    end
+
+    # I decode an incoming web request in to a OpenIDRequest.
+    class Decoder
+
+      @@handlers = {
+        'checkid_setup' => CheckIDRequest.method('from_message'),
+        'checkid_immediate' => CheckIDRequest.method('from_message'),
+        'check_authentication' => CheckAuthRequest.method('from_message'),
+        'associate' => AssociateRequest.method('from_message'),
+        }
+
+      attr_accessor :server
+
+      # Construct a Decoder. The server is necessary because some
+      # replies reference their server.
+      def initialize(server)
+        @server = server
+      end
+
+      # I transform query parameters into an OpenIDRequest.
+      #
+      # If the query does not seem to be an OpenID request at all, I
+      # return nil.
+      #
+      # Raises ProtocolError when the query does not seem to be a valid
+      # OpenID request.
+      def decode(query)
+        if query.nil? or query.length == 0
+          return nil
+        end
+
+        begin
+          message = Message.from_post_args(query)
+        rescue InvalidOpenIDNamespace => e
+          query = query.dup
+          query['openid.ns'] = OPENID2_NS
+          message = Message.from_post_args(query)
+          raise ProtocolError.new(message, e.to_s)
+        end
+
+        mode = message.get_arg(OPENID_NS, 'mode')
+        if !mode
+          msg = sprintf("No mode value in message %s", message)
+          raise ProtocolError.new(message, msg)
+        end
+
+        handler = @@handlers.fetch(mode, self.method('default_decoder'))
+        return handler.call(message, @server.op_endpoint)
+      end
+
+      # Called to decode queries when no handler for that mode is
+      # found.
+      #
+      # This implementation always raises ProtocolError.
+      def default_decoder(message, server)
+        mode = message.get_arg(OPENID_NS, 'mode')
+        msg = sprintf("Unrecognized OpenID mode %s", mode)
+        raise ProtocolError.new(message, msg)
+      end
+    end
+
+    # I handle requests for an OpenID server.
+    #
+    # Some types of requests (those which are not checkid requests)
+    # may be handed to my handleRequest method, and I will take care
+    # of it and return a response.
+    #
+    # For your convenience, I also provide an interface to
+    # Decoder.decode and SigningEncoder.encode through my methods
+    # decodeRequest and encodeResponse.
+    #
+    # All my state is encapsulated in an store, which means I'm not
+    # generally pickleable but I am easy to reconstruct.
+    class Server
+      @@signatoryClass = Signatory
+      @@encoderClass = SigningEncoder
+      @@decoderClass = Decoder
+
+      # The back-end where my associations and nonces are stored.
+      attr_accessor :store
+
+      # I'm using this for associate requests and to sign things.
+      attr_accessor :signatory
+
+      # I'm using this to encode things.
+      attr_accessor :encoder
+
+      # I'm using this to decode things.
+      attr_accessor :decoder
+
+      # I use this instance of OpenID::AssociationNegotiator to
+      # determine which kinds of associations I can make and how.
+      attr_accessor :negotiator
+
+      # My URL.
+      attr_accessor :op_endpoint
+
+      # op_endpoint is new in library version 2.0.
+      def initialize(store, op_endpoint)
+        @store = store
+        @signatory = @@signatoryClass.new(@store)
+        @encoder = @@encoderClass.new(@signatory)
+        @decoder = @@decoderClass.new(self)
+        @negotiator = DefaultNegotiator.copy()
+        @op_endpoint = op_endpoint
+      end
+
+      # Handle a request.
+      #
+      # Give me a request, I will give you a response.  Unless it's a
+      # type of request I cannot handle myself, in which case I will
+      # raise RuntimeError.  In that case, you can handle it yourself,
+      # or add a method to me for handling that request type.
+      def handle_request(request)
+        begin
+          handler = self.method('openid_' + request.mode)
+        rescue NameError
+          raise RuntimeError.new(
+            sprintf("%s has no handler for a request of mode %s.",
+                    self, request.mode))
+        end
+
+        return handler.call(request)
+      end
+
+      # Handle and respond to check_authentication requests.
+      def openid_check_authentication(request)
+        return request.answer(@signatory)
+      end
+
+      # Handle and respond to associate requests.
+      def openid_associate(request)
+        assoc_type = request.assoc_type
+        session_type = request.session.session_type
+        if @negotiator.allowed?(assoc_type, session_type)
+          assoc = @signatory.create_association(false,
+                                                assoc_type)
+          return request.answer(assoc)
+        else
+          message = sprintf('Association type %s is not supported with ' +
+                            'session type %s', assoc_type, session_type)
+          preferred_assoc_type, preferred_session_type = @negotiator.get_allowed_type()
+          return request.answer_unsupported(message,
+                                            preferred_assoc_type,
+                                            preferred_session_type)
+        end
+      end
+
+      # Transform query parameters into an OpenIDRequest.
+      # query should contain the query parameters as a Hash with
+      # each key mapping to one value.
+      #
+      # If the query does not seem to be an OpenID request at all, I
+      # return nil.
+      def decode_request(query)
+        return @decoder.decode(query)
+      end
+
+      # Encode a response to a WebResponse, signing it first if
+      # appropriate.
+      #
+      # Raises EncodingError when I can't figure out how to encode this
+      # message.
+      #
+      # Raises AlreadySigned When this response is already signed.
+      def encode_response(response)
+        return @encoder.encode(response)
+      end
+    end
+
+    # A message did not conform to the OpenID protocol.
+    class ProtocolError < Exception
+      # The query that is failing to be a valid OpenID request.
+      attr_accessor :openid_message
+      attr_accessor :reference
+      attr_accessor :contact
+
+      # text:: A message about the encountered error.
+      def initialize(message, text=nil, reference=nil, contact=nil)
+        @openid_message = message
+        @reference = reference
+        @contact = contact
+        Util.assert(!message.is_a?(String))
+        super(text)
+      end
+
+      # Get the return_to argument from the request, if any.
+      def get_return_to
+        if @openid_message.nil?
+          return nil
+        else
+          return @openid_message.get_arg(OPENID_NS, 'return_to')
+        end
+      end
+
+      # Did this request have a return_to parameter?
+      def has_return_to
+        return !get_return_to.nil?
+      end
+
+      # Generate a Message object for sending to the relying party,
+      # after encoding.
+      def to_message
+        namespace = @openid_message.get_openid_namespace()
+        reply = Message.new(namespace)
+        reply.set_arg(OPENID_NS, 'mode', 'error')
+        reply.set_arg(OPENID_NS, 'error', self.to_s)
+
+        if @contact
+          reply.set_arg(OPENID_NS, 'contact', @contact.to_s)
+        end
+
+        if @reference
+          reply.set_arg(OPENID_NS, 'reference', @reference.to_s)
+        end
+
+        return reply
+      end
+
+      # implements IEncodable
+
+      def encode_to_url
+        return to_message().to_url(get_return_to())
+      end
+
+      def encode_to_kvform
+        return to_message().to_kvform()
+      end
+
+      def to_form_markup
+        return to_message().to_form_markup(get_return_to())
+      end
+
+      def to_html
+        return Util.auto_submit_html(to_form_markup)
+      end
+
+      # How should I be encoded?
+      #
+      # Returns one of ENCODE_URL, ENCODE_KVFORM, or None.  If None,
+      # I cannot be encoded as a protocol message and should be
+      # displayed to the user.
+      def which_encoding
+        if has_return_to()
+          if @openid_message.is_openid2 and
+              encode_to_url().length > OPENID1_URL_LIMIT
+            return ENCODE_HTML_FORM
+          else
+            return ENCODE_URL
+          end
+        end
+
+        if @openid_message.nil?
+          return nil
+        end
+
+        mode = @openid_message.get_arg(OPENID_NS, 'mode')
+        if mode
+          if !BROWSER_REQUEST_MODES.member?(mode)
+            return ENCODE_KVFORM
+          end
+        end
+
+        # If your request was so broken that you didn't manage to
+        # include an openid.mode, I'm not going to worry too much
+        # about returning you something you can't parse.
+        return nil
+      end
+    end
+
+    # Raised when an operation was attempted that is not compatible
+    # with the protocol version being used.
+    class VersionError < Exception
+    end
+
+    # Raised when a response to a request cannot be generated
+    # because the request contains no return_to URL.
+    class NoReturnToError < Exception
+    end
+
+    # Could not encode this as a protocol message.
+    #
+    # You should probably render it and show it to the user.
+    class EncodingError < Exception
+      # The response that failed to encode.
+      attr_reader :response
+
+      def initialize(response)
+        super(response)
+        @response = response
+      end
+    end
+
+    # This response is already signed.
+    class AlreadySigned < EncodingError
+    end
+
+    # A return_to is outside the trust_root.
+    class UntrustedReturnURL < ProtocolError
+      attr_reader :return_to, :trust_root
+
+      def initialize(message, return_to, trust_root)
+        super(message)
+        @return_to = return_to
+        @trust_root = trust_root
+      end
+
+      def to_s
+        return sprintf("return_to %s not under trust_root %s",
+                       @return_to,
+                       @trust_root)
+      end
+    end
+
+    # The return_to URL doesn't look like a valid URL.
+    class MalformedReturnURL < ProtocolError
+      attr_reader :return_to
+
+      def initialize(openid_message, return_to)
+        @return_to = return_to
+        super(openid_message)
+      end
+    end
+
+    # The trust root is not well-formed.
+    class MalformedTrustRoot < ProtocolError
+    end
+  end
+end
diff --git a/vendor/gems/ruby-openid-2.1.4/lib/openid/store/filesystem.rb b/vendor/gems/ruby-openid-2.1.4/lib/openid/store/filesystem.rb
new file mode 100644 (file)
index 0000000..e2993ee
--- /dev/null
@@ -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
+
diff --git a/vendor/gems/ruby-openid-2.1.4/lib/openid/store/interface.rb b/vendor/gems/ruby-openid-2.1.4/lib/openid/store/interface.rb
new file mode 100644 (file)
index 0000000..50819f6
--- /dev/null
@@ -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
diff --git a/vendor/gems/ruby-openid-2.1.4/lib/openid/store/memory.rb b/vendor/gems/ruby-openid-2.1.4/lib/openid/store/memory.rb
new file mode 100644 (file)
index 0000000..58455b9
--- /dev/null
@@ -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
diff --git a/vendor/gems/ruby-openid-2.1.4/lib/openid/store/nonce.rb b/vendor/gems/ruby-openid-2.1.4/lib/openid/store/nonce.rb
new file mode 100644 (file)
index 0000000..08a9e52
--- /dev/null
@@ -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
diff --git a/vendor/gems/ruby-openid-2.1.4/lib/openid/trustroot.rb b/vendor/gems/ruby-openid-2.1.4/lib/openid/trustroot.rb
new file mode 100644 (file)
index 0000000..16695f0
--- /dev/null
@@ -0,0 +1,349 @@
+require 'uri'
+require 'openid/urinorm'
+
+module OpenID
+
+  class RealmVerificationRedirected < Exception
+    # Attempting to verify this realm resulted in a redirect.
+    def initialize(relying_party_url, rp_url_after_redirects)
+      @relying_party_url = relying_party_url
+      @rp_url_after_redirects = rp_url_after_redirects
+    end
+
+    def to_s
+      return "Attempting to verify #{@relying_party_url} resulted in " +
+        "redirect to #{@rp_url_after_redirects}"
+    end
+  end
+
+  module TrustRoot
+    TOP_LEVEL_DOMAINS = %w'
+      ac ad ae aero af ag ai al am an ao aq ar arpa as asia at
+      au aw ax az ba bb bd be bf bg bh bi biz bj bm bn bo br bs bt
+      bv bw by bz ca cat cc cd cf cg ch ci ck cl cm cn co com coop
+      cr cu cv cx cy cz de dj dk dm do dz ec edu ee eg er es et eu
+      fi fj fk fm fo fr ga gb gd ge gf gg gh gi gl gm gn gov gp gq
+      gr gs gt gu gw gy hk hm hn hr ht hu id ie il im in info int
+      io iq ir is it je jm jo jobs jp ke kg kh ki km kn kp kr kw
+      ky kz la lb lc li lk lr ls lt lu lv ly ma mc md me mg mh mil
+      mk ml mm mn mo mobi mp mq mr ms mt mu museum mv mw mx my mz
+      na name nc ne net nf ng ni nl no np nr nu nz om org pa pe pf
+      pg ph pk pl pm pn pr pro ps pt pw py qa re ro rs ru rw sa sb
+      sc sd se sg sh si sj sk sl sm sn so sr st su sv sy sz tc td
+      tel tf tg th tj tk tl tm tn to tp tr travel tt tv tw tz ua
+      ug uk us uy uz va vc ve vg vi vn vu wf ws xn--0zwm56d
+      xn--11b5bs3a9aj6g xn--80akhbyknj4f xn--9t4b11yi5a
+      xn--deba0ad xn--g6w251d xn--hgbk6aj7f53bba
+      xn--hlcj6aya9esc7a xn--jxalpdlp xn--kgbechtv xn--zckzah ye
+      yt yu za zm zw'
+
+    ALLOWED_PROTOCOLS = ['http', 'https']
+
+    # The URI for relying party discovery, used in realm verification.
+    #
+    # XXX: This should probably live somewhere else (like in
+    # OpenID or OpenID::Yadis somewhere)
+    RP_RETURN_TO_URL_TYPE = 'http://specs.openid.net/auth/2.0/return_to'
+
+    # If the endpoint is a relying party OpenID return_to endpoint,
+    # return the endpoint URL. Otherwise, return None.
+    #
+    # This function is intended to be used as a filter for the Yadis
+    # filtering interface.
+    #
+    # endpoint: An XRDS BasicServiceEndpoint, as returned by
+    # performing Yadis dicovery.
+    #
+    # returns the endpoint URL or None if the endpoint is not a
+    # relying party endpoint.
+    def TrustRoot._extract_return_url(endpoint)
+      if endpoint.matchTypes([RP_RETURN_TO_URL_TYPE])
+        return endpoint.uri
+      else
+        return nil
+      end
+    end
+
+    # Is the return_to URL under one of the supplied allowed
+    # return_to URLs?
+    def TrustRoot.return_to_matches(allowed_return_to_urls, return_to)
+      allowed_return_to_urls.each { |allowed_return_to|
+        # A return_to pattern works the same as a realm, except that
+        # it's not allowed to use a wildcard. We'll model this by
+        # parsing it as a realm, and not trying to match it if it has
+        # a wildcard.
+
+        return_realm = TrustRoot.parse(allowed_return_to)
+        if (# Parses as a trust root
+            !return_realm.nil? and
+
+            # Does not have a wildcard
+            !return_realm.wildcard and
+
+            # Matches the return_to that we passed in with it
+            return_realm.validate_url(return_to)
+            )
+          return true
+        end
+      }
+
+      # No URL in the list matched
+      return false
+    end
+
+    # Given a relying party discovery URL return a list of return_to
+    # URLs.
+    def TrustRoot.get_allowed_return_urls(relying_party_url)
+      rp_url_after_redirects, return_to_urls = services.get_service_endpoints(
+        relying_party_url, _extract_return_url)
+
+      if rp_url_after_redirects != relying_party_url
+        # Verification caused a redirect
+        raise RealmVerificationRedirected.new(
+                relying_party_url, rp_url_after_redirects)
+      end
+
+      return return_to_urls
+    end
+
+    # Verify that a return_to URL is valid for the given realm.
+    #
+    # This function builds a discovery URL, performs Yadis discovery
+    # on it, makes sure that the URL does not redirect, parses out
+    # the return_to URLs, and finally checks to see if the current
+    # return_to URL matches the return_to.
+    #
+    # raises DiscoveryFailure when Yadis discovery fails returns
+    # true if the return_to URL is valid for the realm
+    def TrustRoot.verify_return_to(realm_str, return_to, _vrfy=nil)
+      # _vrfy parameter is there to make testing easier
+      if _vrfy.nil?
+        _vrfy = self.method('get_allowed_return_urls')
+      end
+
+      if !(_vrfy.is_a?(Proc) or _vrfy.is_a?(Method))
+        raise ArgumentError, "_vrfy must be a Proc or Method"
+      end
+
+      realm = TrustRoot.parse(realm_str)
+      if realm.nil?
+        # The realm does not parse as a URL pattern
+        return false
+      end
+
+      begin
+        allowable_urls = _vrfy.call(realm.build_discovery_url())
+      rescue RealmVerificationRedirected => err
+        Util.log(err.to_s)
+        return false
+      end
+
+      if return_to_matches(allowable_urls, return_to)
+        return true
+      else
+        Util.log("Failed to validate return_to #{return_to} for " +
+            "realm #{realm_str}, was not in #{allowable_urls}")
+        return false
+      end
+    end
+
+    class TrustRoot
+
+      attr_reader :unparsed, :proto, :wildcard, :host, :port, :path
+
+      @@empty_re = Regexp.new('^http[s]*:\/\/\*\/$')
+
+      def TrustRoot._build_path(path, query=nil, frag=nil)
+        s = path.dup
+
+        frag = nil if frag == ''
+        query = nil if query == ''
+
+        if query
+          s << "?" << query
+        end
+
+        if frag
+          s << "#" << frag
+        end
+
+        return s
+      end
+
+      def TrustRoot._parse_url(url)
+        begin
+          url = URINorm.urinorm(url)
+        rescue URI::InvalidURIError => err
+          nil
+        end
+
+        begin
+          parsed = URI::parse(url)
+        rescue URI::InvalidURIError
+          return nil
+        end
+
+        path = TrustRoot._build_path(parsed.path,
+                                     parsed.query,
+                                     parsed.fragment)
+
+        return [parsed.scheme || '', parsed.host || '',
+                parsed.port || '', path || '']
+      end
+
+      def TrustRoot.parse(trust_root)
+        trust_root = trust_root.dup
+        unparsed = trust_root.dup
+
+        # look for wildcard
+        wildcard = (not trust_root.index('://*.').nil?)
+        trust_root.sub!('*.', '') if wildcard
+
+        # handle http://*/ case
+        if not wildcard and @@empty_re.match(trust_root)
+          proto = trust_root.split(':')[0]
+          port = proto == 'http' ? 80 : 443
+          return new(unparsed, proto, true, '', port, '/')
+        end
+
+        parts = TrustRoot._parse_url(trust_root)
+        return nil if parts.nil?
+
+        proto, host, port, path = parts
+
+        # check for URI fragment
+        if path and !path.index('#').nil?
+          return nil
+        end
+
+        return nil unless ['http', 'https'].member?(proto)
+        return new(unparsed, proto, wildcard, host, port, path)
+      end
+
+      def TrustRoot.check_sanity(trust_root_string)
+        trust_root = TrustRoot.parse(trust_root_string)
+        if trust_root.nil?
+          return false
+        else
+          return trust_root.sane?
+        end
+      end
+
+      # quick func for validating a url against a trust root.  See the
+      # TrustRoot class if you need more control.
+      def self.check_url(trust_root, url)
+        tr = self.parse(trust_root)
+        return (!tr.nil? and tr.validate_url(url))
+      end
+
+      # Return a discovery URL for this realm.
+      #
+      # This function does not check to make sure that the realm is
+      # valid. Its behaviour on invalid inputs is undefined.
+      #
+      # return_to:: The relying party return URL of the OpenID
+      # authentication request
+      #
+      # Returns the URL upon which relying party discovery should be
+      # run in order to verify the return_to URL
+      def build_discovery_url
+        if self.wildcard
+          # Use "www." in place of the star
+          www_domain = 'www.' + @host
+          port = (!@port.nil? and ![80, 443].member?(@port)) ? (":" + @port.to_s) : ''
+          return "#{@proto}://#{www_domain}#{port}#{@path}"
+        else
+          return @unparsed
+        end
+      end
+
+      def initialize(unparsed, proto, wildcard, host, port, path)
+        @unparsed = unparsed
+        @proto = proto
+        @wildcard = wildcard
+        @host = host
+        @port = port
+        @path = path
+      end
+
+      def sane?
+        return true if @host == 'localhost'
+
+        host_parts = @host.split('.')
+
+        # a note: ruby string split does not put an empty string at
+        # the end of the list if the split element is last.  for
+        # example, 'foo.com.'.split('.') => ['foo','com'].  Mentioned
+        # because the python code differs here.
+
+        return false if host_parts.length == 0
+
+        # no adjacent dots
+        return false if host_parts.member?('')
+
+        # last part must be a tld
+        tld = host_parts[-1]
+        return false unless TOP_LEVEL_DOMAINS.member?(tld)
+
+        return false if host_parts.length == 1
+
+        if @wildcard
+          if tld.length == 2 and host_parts[-2].length <= 3
+            # It's a 2-letter tld with a short second to last segment
+            # so there needs to be more than two segments specified
+            # (e.g. *.co.uk is insane)
+            return host_parts.length > 2
+          end
+        end
+
+        return true
+      end
+
+      def validate_url(url)
+        parts = TrustRoot._parse_url(url)
+        return false if parts.nil?
+
+        proto, host, port, path = parts
+
+        return false unless proto == @proto
+        return false unless port == @port
+        return false unless host.index('*').nil?
+
+        if !@wildcard
+          if host != @host
+            return false
+          end
+        elsif ((@host != '') and
+               (!host.ends_with?('.' + @host)) and
+               (host != @host))
+          return false
+        end
+
+        if path != @path
+          path_len = @path.length
+          trust_prefix = @path[0...path_len]
+          url_prefix = path[0...path_len]
+
+          # must be equal up to the length of the path, at least
+          if trust_prefix != url_prefix
+            return false
+          end
+
+          # These characters must be on the boundary between the end
+          # of the trust root's path and the start of the URL's path.
+          if !@path.index('?').nil?
+            allowed = '&'
+          else
+            allowed = '?/'
+          end
+
+          return (!allowed.index(@path[-1]).nil? or
+                  !allowed.index(path[path_len]).nil?)
+        end
+
+        return true
+      end
+    end
+  end
+end
+
diff --git a/vendor/gems/ruby-openid-2.1.4/lib/openid/urinorm.rb b/vendor/gems/ruby-openid-2.1.4/lib/openid/urinorm.rb
new file mode 100644 (file)
index 0000000..f30893e
--- /dev/null
@@ -0,0 +1,75 @@
+require 'uri'
+
+require "openid/extras"
+
+module OpenID
+
+  module URINorm
+    public
+    def URINorm.urinorm(uri)
+      uri = URI.parse(uri)
+
+      raise URI::InvalidURIError.new('no scheme') unless uri.scheme
+      uri.scheme = uri.scheme.downcase
+      unless ['http','https'].member?(uri.scheme)
+        raise URI::InvalidURIError.new('Not an HTTP or HTTPS URI')
+      end
+
+      raise URI::InvalidURIError.new('no host') unless uri.host
+      uri.host = uri.host.downcase
+
+      uri.path = remove_dot_segments(uri.path)
+      uri.path = '/' if uri.path.length == 0
+
+      uri = uri.normalize.to_s
+      uri = uri.gsub(PERCENT_ESCAPE_RE) {
+        sub = $&[1..2].to_i(16).chr
+        reserved(sub) ? $&.upcase : sub
+      }
+
+      return uri
+    end
+
+    private
+    RESERVED_RE = /[A-Za-z0-9._~-]/
+    PERCENT_ESCAPE_RE = /%[0-9a-zA-Z]{2}/
+
+    def URINorm.reserved(chr)
+      not RESERVED_RE =~ chr
+    end
+
+    def URINorm.remove_dot_segments(path)
+      result_segments = []
+
+      while path.length > 0
+        if path.starts_with?('../')
+          path = path[3..-1]
+        elsif path.starts_with?('./')
+          path = path[2..-1]
+        elsif path.starts_with?('/./')
+          path = path[2..-1]
+        elsif path == '/.'
+          path = '/'
+        elsif path.starts_with?('/../')
+          path = path[3..-1]
+          result_segments.pop if result_segments.length > 0
+        elsif path == '/..'
+          path = '/'
+          result_segments.pop if result_segments.length > 0
+        elsif path == '..' or path == '.'
+          path = ''
+        else
+          i = 0
+          i = 1 if path[0].chr == '/'
+          i = path.index('/', i)
+          i = path.length if i.nil?
+          result_segments << path[0...i]
+          path = path[i..-1]
+        end
+      end
+
+      return result_segments.join('')
+    end
+  end
+
+end
diff --git a/vendor/gems/ruby-openid-2.1.4/lib/openid/util.rb b/vendor/gems/ruby-openid-2.1.4/lib/openid/util.rb
new file mode 100644 (file)
index 0000000..c5a6716
--- /dev/null
@@ -0,0 +1,110 @@
+require "cgi"
+require "uri"
+require "logger"
+
+require "openid/extras"
+
+# See OpenID::Consumer or OpenID::Server modules, as well as the store classes
+module OpenID
+  class AssertionError < Exception
+  end
+
+  # Exceptions that are raised by the library are subclasses of this
+  # exception type, so if you want to catch all exceptions raised by
+  # the library, you can catch OpenIDError
+  class OpenIDError < StandardError
+  end
+
+  module Util
+
+    BASE64_CHARS = ('ABCDEFGHIJKLMNOPQRSTUVWXYZ' \
+                    'abcdefghijklmnopqrstuvwxyz0123456789+/')
+    BASE64_RE = Regexp.compile("
+    \\A
+    ([#{BASE64_CHARS}]{4})*
+    ([#{BASE64_CHARS}]{2}==|
+     [#{BASE64_CHARS}]{3}=)?
+    \\Z", Regexp::EXTENDED)
+
+    def Util.assert(value, message=nil)
+      if not value
+        raise AssertionError, message or value
+      end
+    end
+
+    def Util.to_base64(s)
+      [s].pack('m').gsub("\n", "")
+    end
+
+    def Util.from_base64(s)
+      without_newlines = s.gsub(/[\r\n]+/, '')
+      if !BASE64_RE.match(without_newlines)
+        raise ArgumentError, "Malformed input: #{s.inspect}"
+      end
+      without_newlines.unpack('m').first
+    end
+
+    def Util.urlencode(args)
+      a = []
+      args.each do |key, val|
+        val = '' unless val
+        a << (CGI::escape(key) + "=" + CGI::escape(val))
+      end
+      a.join("&")
+    end
+
+    def Util.parse_query(qs)
+      query = {}
+      CGI::parse(qs).each {|k,v| query[k] = v[0]}
+      return query
+    end
+
+    def Util.append_args(url, args)
+      url = url.dup
+      return url if args.length == 0
+
+      if args.respond_to?('each_pair')
+        args = args.sort
+      end
+
+      url << (url.include?("?") ? "&" : "?")
+      url << Util.urlencode(args)
+    end
+
+    @@logger = Logger.new(STDERR)
+    @@logger.progname = "OpenID"
+
+    def Util.logger=(logger)
+      @@logger = logger
+    end
+
+    def Util.logger
+      @@logger
+    end
+
+    # change the message below to do whatever you like for logging
+    def Util.log(message)
+      logger.info(message)
+    end
+
+    def Util.auto_submit_html(form, title='OpenID transaction in progress')
+      return "
+<html>
+<head>
+  <title>#{title}</title>
+</head>
+<body onload='document.forms[0].submit();'>
+#{form}
+<script>
+var elements = document.forms[0].elements;
+for (var i = 0; i < elements.length; i++) {
+  elements[i].style.display = \"none\";
+}
+</script>
+</body>
+</html>
+"
+    end
+  end
+
+end
diff --git a/vendor/gems/ruby-openid-2.1.4/lib/openid/yadis/accept.rb b/vendor/gems/ruby-openid-2.1.4/lib/openid/yadis/accept.rb
new file mode 100644 (file)
index 0000000..a165748
--- /dev/null
@@ -0,0 +1,148 @@
+module OpenID
+
+  module Yadis
+
+    # Generate an accept header value
+    #
+    # [str or (str, float)] -> str
+    def self.generate_accept_header(*elements)
+      parts = []
+      elements.each { |element|
+        if element.is_a?(String)
+          qs = "1.0"
+          mtype = element
+        else
+          mtype, q = element
+          q = q.to_f
+          if q > 1 or q <= 0
+            raise ArgumentError.new("Invalid preference factor: #{q}")
+          end
+          qs = sprintf("%0.1f", q)
+        end
+
+        parts << [qs, mtype]
+      }
+
+      parts.sort!
+      chunks = []
+      parts.each { |q, mtype|
+        if q == '1.0'
+          chunks << mtype
+        else
+          chunks << sprintf("%s; q=%s", mtype, q)
+        end
+      }
+
+      return chunks.join(', ')
+    end
+
+    def self.parse_accept_header(value)
+      # Parse an accept header, ignoring any accept-extensions
+      #
+      # returns a list of tuples containing main MIME type, MIME
+      # subtype, and quality markdown.
+      #
+      # str -> [(str, str, float)]
+      chunks = value.split(',', -1).collect { |v| v.strip }
+      accept = []
+      chunks.each { |chunk|
+        parts = chunk.split(";", -1).collect { |s| s.strip }
+
+        mtype = parts.shift
+        if mtype.index('/').nil?
+          # This is not a MIME type, so ignore the bad data
+          next
+        end
+
+        main, sub = mtype.split('/', 2)
+
+        q = nil
+        parts.each { |ext|
+          if !ext.index('=').nil?
+            k, v = ext.split('=', 2)
+            if k == 'q'
+              q = v.to_f
+            end
+          end
+        }
+
+        q = 1.0 if q.nil?
+
+        accept << [q, main, sub]
+      }
+
+      accept.sort!
+      accept.reverse!
+
+      return accept.collect { |q, main, sub| [main, sub, q] }
+    end
+
+    def self.match_types(accept_types, have_types)
+      # Given the result of parsing an Accept: header, and the
+      # available MIME types, return the acceptable types with their
+      # quality markdowns.
+      #
+      # For example:
+      #
+      # >>> acceptable = parse_accept_header('text/html, text/plain; q=0.5')
+      # >>> matchTypes(acceptable, ['text/plain', 'text/html', 'image/jpeg'])
+      # [('text/html', 1.0), ('text/plain', 0.5)]
+      #
+      # Type signature: ([(str, str, float)], [str]) -> [(str, float)]
+      if accept_types.nil? or accept_types == []
+        # Accept all of them
+        default = 1
+      else
+        default = 0
+      end
+
+      match_main = {}
+      match_sub = {}
+      accept_types.each { |main, sub, q|
+        if main == '*'
+          default = [default, q].max
+          next
+        elsif sub == '*'
+          match_main[main] = [match_main.fetch(main, 0), q].max
+        else
+          match_sub[[main, sub]] = [match_sub.fetch([main, sub], 0), q].max
+        end
+      }
+
+      accepted_list = []
+      order_maintainer = 0
+      have_types.each { |mtype|
+        main, sub = mtype.split('/', 2)
+        if match_sub.member?([main, sub])
+          q = match_sub[[main, sub]]
+        else
+          q = match_main.fetch(main, default)
+        end
+
+        if q != 0
+          accepted_list << [1 - q, order_maintainer, q, mtype]
+          order_maintainer += 1
+        end
+      }
+
+      accepted_list.sort!
+      return accepted_list.collect { |_, _, q, mtype| [mtype, q] }
+    end
+
+    def self.get_acceptable(accept_header, have_types)
+      # Parse the accept header and return a list of available types
+      # in preferred order. If a type is unacceptable, it will not be
+      # in the resulting list.
+      #
+      # This is a convenience wrapper around matchTypes and
+      # parse_accept_header
+      #
+      # (str, [str]) -> [str]
+      accepted = self.parse_accept_header(accept_header)
+      preferred = self.match_types(accepted, have_types)
+      return preferred.collect { |mtype, _| mtype }
+    end
+
+  end
+
+end
diff --git a/vendor/gems/ruby-openid-2.1.4/lib/openid/yadis/constants.rb b/vendor/gems/ruby-openid-2.1.4/lib/openid/yadis/constants.rb
new file mode 100644 (file)
index 0000000..99b58b1
--- /dev/null
@@ -0,0 +1,21 @@
+
+require 'openid/yadis/accept'
+
+module OpenID
+
+  module Yadis
+
+    YADIS_HEADER_NAME = 'X-XRDS-Location'
+    YADIS_CONTENT_TYPE = 'application/xrds+xml'
+
+    # A value suitable for using as an accept header when performing
+    # YADIS discovery, unless the application has special requirements
+    YADIS_ACCEPT_HEADER = generate_accept_header(
+                                                 ['text/html', 0.3],
+                                                 ['application/xhtml+xml', 0.5],
+                                                 [YADIS_CONTENT_TYPE, 1.0]
+                                                 )
+
+  end
+
+end
diff --git a/vendor/gems/ruby-openid-2.1.4/lib/openid/yadis/discovery.rb b/vendor/gems/ruby-openid-2.1.4/lib/openid/yadis/discovery.rb
new file mode 100644 (file)
index 0000000..55d6f09
--- /dev/null
@@ -0,0 +1,153 @@
+
+require 'openid/util'
+require 'openid/fetchers'
+require 'openid/yadis/constants'
+require 'openid/yadis/parsehtml'
+
+module OpenID
+
+  # Raised when a error occurs in the discovery process
+  class DiscoveryFailure < OpenIDError
+    attr_accessor :identity_url, :http_response
+
+    def initialize(message, http_response)
+      super(message)
+      @identity_url = nil
+      @http_response = http_response
+    end
+  end
+
+  module Yadis
+
+    # Contains the result of performing Yadis discovery on a URI
+    class DiscoveryResult
+
+      # The result of following redirects from the request_uri
+      attr_accessor :normalize_uri
+
+      # The URI from which the response text was returned (set to
+      # nil if there was no XRDS document found)
+      attr_accessor :xrds_uri
+
+      # The content-type returned with the response_text
+      attr_accessor :content_type
+
+      # The document returned from the xrds_uri
+      attr_accessor :response_text
+
+      attr_accessor :request_uri, :normalized_uri
+
+      def initialize(request_uri)
+        # Initialize the state of the object
+        #
+        # sets all attributes to None except the request_uri
+        @request_uri = request_uri
+        @normalized_uri = nil
+        @xrds_uri = nil
+        @content_type = nil
+        @response_text = nil
+      end
+
+      # Was the Yadis protocol's indirection used?
+      def used_yadis_location?
+        return @normalized_uri != @xrds_uri
+      end
+
+      # Is the response text supposed to be an XRDS document?
+      def is_xrds
+        return (used_yadis_location?() or
+                @content_type == YADIS_CONTENT_TYPE)
+      end
+    end
+
+    # Discover services for a given URI.
+    #
+    # uri: The identity URI as a well-formed http or https URI. The
+    # well-formedness and the protocol are not checked, but the
+    # results of this function are undefined if those properties do
+    # not hold.
+    #
+    # returns a DiscoveryResult object
+    #
+    # Raises DiscoveryFailure when the HTTP response does not have
+    # a 200 code.
+    def self.discover(uri)
+      result = DiscoveryResult.new(uri)
+      begin
+        resp = OpenID.fetch(uri, nil, {'Accept' => YADIS_ACCEPT_HEADER})
+      rescue Exception
+        raise DiscoveryFailure.new("Failed to fetch identity URL #{uri} : #{$!}", $!)
+      end
+      if resp.code != "200" and resp.code != "206"
+        raise DiscoveryFailure.new(
+                "HTTP Response status from identity URL host is not \"200\"."\
+                "Got status #{resp.code.inspect} for #{resp.final_url}", resp)
+      end
+
+      # Note the URL after following redirects
+      result.normalized_uri = resp.final_url
+
+      # Attempt to find out where to go to discover the document or if
+      # we already have it
+      result.content_type = resp['content-type']
+
+      result.xrds_uri = self.where_is_yadis?(resp)
+
+      if result.xrds_uri and result.used_yadis_location?
+        begin
+          resp = OpenID.fetch(result.xrds_uri)
+        rescue
+          raise DiscoveryFailure.new("Failed to fetch Yadis URL #{result.xrds_uri} : #{$!}", $!)
+        end
+        if resp.code != "200" and resp.code != "206"
+            exc = DiscoveryFailure.new(
+                    "HTTP Response status from Yadis host is not \"200\". " +
+                                       "Got status #{resp.code.inspect} for #{resp.final_url}", resp)
+            exc.identity_url = result.normalized_uri
+            raise exc
+        end
+
+        result.content_type = resp['content-type']
+      end
+
+      result.response_text = resp.body
+      return result
+    end
+
+    # Given a HTTPResponse, return the location of the Yadis
+    # document.
+    #
+    # May be the URL just retrieved, another URL, or None, if I
+    # can't find any.
+    #
+    # [non-blocking]
+    def self.where_is_yadis?(resp)
+      # Attempt to find out where to go to discover the document or if
+      # we already have it
+      content_type = resp['content-type']
+
+      # According to the spec, the content-type header must be an
+      # exact match, or else we have to look for an indirection.
+      if (!content_type.nil? and !content_type.to_s.empty? and
+          content_type.split(';', 2)[0].downcase == YADIS_CONTENT_TYPE)
+        return resp.final_url
+      else
+        # Try the header
+        yadis_loc = resp[YADIS_HEADER_NAME.downcase]
+
+        if yadis_loc.nil?
+          # Parse as HTML if the header is missing.
+          #
+          # XXX: do we want to do something with content-type, like
+          # have a whitelist or a blacklist (for detecting that it's
+          # HTML)?
+          yadis_loc = Yadis.html_yadis_location(resp.body)
+        end
+      end
+
+      return yadis_loc
+    end
+
+  end
+
+end
diff --git a/vendor/gems/ruby-openid-2.1.4/lib/openid/yadis/filters.rb b/vendor/gems/ruby-openid-2.1.4/lib/openid/yadis/filters.rb
new file mode 100644 (file)
index 0000000..90f350e
--- /dev/null
@@ -0,0 +1,205 @@
+# This file contains functions and classes used for extracting
+# endpoint information out of a Yadis XRD file using the REXML
+# XML parser.
+
+#
+module OpenID
+  module Yadis
+    class BasicServiceEndpoint
+      attr_reader :type_uris, :yadis_url, :uri, :service_element
+
+      # Generic endpoint object that contains parsed service
+      # information, as well as a reference to the service element
+      # from which it was generated. If there is more than one
+      # xrd:Type or xrd:URI in the xrd:Service, this object represents
+      # just one of those pairs.
+      #
+      # This object can be used as a filter, because it implements
+      # fromBasicServiceEndpoint.
+      #
+      # The simplest kind of filter you can write implements
+      # fromBasicServiceEndpoint, which takes one of these objects.
+      def initialize(yadis_url, type_uris, uri, service_element)
+        @type_uris = type_uris
+        @yadis_url = yadis_url
+        @uri = uri
+        @service_element = service_element
+      end
+
+      # Query this endpoint to see if it has any of the given type
+      # URIs. This is useful for implementing other endpoint classes
+      # that e.g. need to check for the presence of multiple
+      # versions of a single protocol.
+      def match_types(type_uris)
+        return @type_uris & type_uris
+      end
+
+      # Trivial transform from a basic endpoint to itself. This
+      # method exists to allow BasicServiceEndpoint to be used as a
+      # filter.
+      #
+      # If you are subclassing this object, re-implement this function.
+      def self.from_basic_service_endpoint(endpoint)
+        return endpoint
+      end
+
+      # A hack to make both this class and its instances respond to
+      # this message since Ruby doesn't support static methods.
+      def from_basic_service_endpoint(endpoint)
+        return self.class.from_basic_service_endpoint(endpoint)
+      end
+
+    end
+
+    # Take a list of basic filters and makes a filter that
+    # transforms the basic filter into a top-level filter. This is
+    # mostly useful for the implementation of make_filter, which
+    # should only be needed for special cases or internal use by
+    # this library.
+    #
+    # This object is useful for creating simple filters for services
+    # that use one URI and are specified by one Type (we expect most
+    # Types will fit this paradigm).
+    #
+    # Creates a BasicServiceEndpoint object and apply the filter
+    # functions to it until one of them returns a value.
+    class TransformFilterMaker
+      attr_reader :filter_procs
+
+      # Initialize the filter maker's state
+      #
+      # filter_functions are the endpoint transformer
+      # Procs to apply to the basic endpoint. These are called in
+      # turn until one of them does not return nil, and the result
+      # of that transformer is returned.
+      def initialize(filter_procs)
+        @filter_procs = filter_procs
+      end
+
+      # Returns an array of endpoint objects produced by the
+      # filter procs.
+      def get_service_endpoints(yadis_url, service_element)
+        endpoints = []
+
+        # Do an expansion of the service element by xrd:Type and
+        # xrd:URI
+        Yadis::expand_service(service_element).each { |type_uris, uri, _|
+          # Create a basic endpoint object to represent this
+          # yadis_url, Service, Type, URI combination
+          endpoint = BasicServiceEndpoint.new(
+                yadis_url, type_uris, uri, service_element)
+
+          e = apply_filters(endpoint)
+          if !e.nil?
+            endpoints << e
+          end
+        }
+        return endpoints
+      end
+
+      def apply_filters(endpoint)
+        # Apply filter procs to an endpoint until one of them returns
+        # non-nil.
+        @filter_procs.each { |filter_proc|
+          e = filter_proc.call(endpoint)
+          if !e.nil?
+            # Once one of the filters has returned an endpoint, do not
+            # apply any more.
+            return e
+          end
+        }
+
+        return nil
+      end
+    end
+
+    class CompoundFilter
+      attr_reader :subfilters
+
+      # Create a new filter that applies a set of filters to an
+      # endpoint and collects their results.
+      def initialize(subfilters)
+        @subfilters = subfilters
+      end
+
+      # Generate all endpoint objects for all of the subfilters of
+      # this filter and return their concatenation.
+      def get_service_endpoints(yadis_url, service_element)
+        endpoints = []
+        @subfilters.each { |subfilter|
+          endpoints += subfilter.get_service_endpoints(yadis_url, service_element)
+        }
+        return endpoints
+      end
+    end
+
+    # Exception raised when something is not able to be turned into a
+    # filter
+    @@filter_type_error = TypeError.new(
+      'Expected a filter, an endpoint, a callable or a list of any of these.')
+
+    # Convert a filter-convertable thing into a filter
+    #
+    # parts should be a filter, an endpoint, a callable, or a list of
+    # any of these.
+    def self.make_filter(parts)
+      # Convert the parts into a list, and pass to mk_compound_filter
+      if parts.nil?
+        parts = [BasicServiceEndpoint]
+      end
+
+      if parts.is_a?(Array)
+        return mk_compound_filter(parts)
+      else
+        return mk_compound_filter([parts])
+      end
+    end
+
+    # Create a filter out of a list of filter-like things
+    #
+    # Used by make_filter
+    #
+    # parts should be a list of things that can be passed to make_filter
+    def self.mk_compound_filter(parts)
+
+      if !parts.respond_to?('each')
+        raise TypeError, "#{parts.inspect} is not iterable"
+      end
+
+      # Separate into a list of callables and a list of filter objects
+      transformers = []
+      filters = []
+      parts.each { |subfilter|
+        if !subfilter.is_a?(Array)
+          # If it's not an iterable
+          if subfilter.respond_to?('get_service_endpoints')
+            # It's a full filter
+            filters << subfilter
+          elsif subfilter.respond_to?('from_basic_service_endpoint')
+            # It's an endpoint object, so put its endpoint conversion
+            # attribute into the list of endpoint transformers
+            transformers << subfilter.method('from_basic_service_endpoint')
+          elsif subfilter.respond_to?('call')
+            # It's a proc, so add it to the list of endpoint
+            # transformers
+            transformers << subfilter
+          else
+            raise @@filter_type_error
+          end
+        else
+          filters << mk_compound_filter(subfilter)
+        end
+      }
+
+      if transformers.length > 0
+        filters << TransformFilterMaker.new(transformers)
+      end
+
+      if filters.length == 1
+        return filters[0]
+      else
+        return CompoundFilter.new(filters)
+      end
+    end
+  end
+end
diff --git a/vendor/gems/ruby-openid-2.1.4/lib/openid/yadis/htmltokenizer.rb b/vendor/gems/ruby-openid-2.1.4/lib/openid/yadis/htmltokenizer.rb
new file mode 100644 (file)
index 0000000..b208109
--- /dev/null
@@ -0,0 +1,305 @@
+# = 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 HTMLTokenizerError, "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 HTMLTokenizerError, "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
+
+class HTMLTokenizerError < Exception
+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 HTMLTokenizerError, "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 HTMLTokenizerError, "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 HTMLTokenizerError, "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
+
diff --git a/vendor/gems/ruby-openid-2.1.4/lib/openid/yadis/parsehtml.rb b/vendor/gems/ruby-openid-2.1.4/lib/openid/yadis/parsehtml.rb
new file mode 100644 (file)
index 0000000..877c714
--- /dev/null
@@ -0,0 +1,45 @@
+require "openid/yadis/htmltokenizer"
+require 'cgi'
+
+module OpenID
+  module Yadis
+    def Yadis.html_yadis_location(html)
+      parser = HTMLTokenizer.new(html)
+
+      # to keep track of whether or not we are in the head element
+      in_head = false
+
+      begin
+        while el = parser.getTag('head', '/head', 'meta', 'body', '/body',
+                                 'html', 'script')
+
+          # we are leaving head or have reached body, so we bail
+          return nil if ['/head', 'body', '/body'].member?(el.tag_name)
+
+          if el.tag_name == 'head'
+            unless el.to_s[-2] == ?/ # tag ends with a /: a short tag
+              in_head = true
+            end
+          end
+          next unless in_head
+
+          if el.tag_name == 'script'
+            unless el.to_s[-2] == ?/ # tag ends with a /: a short tag
+              parser.getTag('/script')
+            end
+          end
+
+          return nil if el.tag_name == 'html'
+
+          if el.tag_name == 'meta' and (equiv = el.attr_hash['http-equiv'])
+            if ['x-xrds-location','x-yadis-location'].member?(equiv.downcase) &&
+                el.attr_hash.member?('content')
+              return CGI::unescapeHTML(el.attr_hash['content'])
+            end
+          end
+        end
+      rescue HTMLTokenizerError # just stop parsing if there's an error
+      end
+    end
+  end
+end
diff --git a/vendor/gems/ruby-openid-2.1.4/lib/openid/yadis/services.rb b/vendor/gems/ruby-openid-2.1.4/lib/openid/yadis/services.rb
new file mode 100644 (file)
index 0000000..e3b3e0f
--- /dev/null
@@ -0,0 +1,42 @@
+
+require 'openid/yadis/filters'
+require 'openid/yadis/discovery'
+require 'openid/yadis/xrds'
+
+module OpenID
+  module Yadis
+    def Yadis.get_service_endpoints(input_url, flt=nil)
+      # Perform the Yadis protocol on the input URL and return an
+      # iterable of resulting endpoint objects.
+      #
+      # @param flt: A filter object or something that is convertable
+      # to a filter object (using mkFilter) that will be used to
+      # generate endpoint objects. This defaults to generating
+      # BasicEndpoint objects.
+      result = Yadis.discover(input_url)
+      begin
+        endpoints = Yadis.apply_filter(result.normalized_uri,
+                                       result.response_text, flt)
+      rescue XRDSError => err
+        raise DiscoveryFailure.new(err.to_s, nil)
+      end
+
+      return [result.normalized_uri, endpoints]
+    end
+
+    def Yadis.apply_filter(normalized_uri, xrd_data, flt=nil)
+      # Generate an iterable of endpoint objects given this input data,
+      # presumably from the result of performing the Yadis protocol.
+
+      flt = Yadis.make_filter(flt)
+      et = Yadis.parseXRDS(xrd_data)
+
+      endpoints = []
+      each_service(et) { |service_element|
+        endpoints += flt.get_service_endpoints(normalized_uri, service_element)
+      }
+
+      return endpoints
+    end
+  end
+end
diff --git a/vendor/gems/ruby-openid-2.1.4/lib/openid/yadis/xrds.rb b/vendor/gems/ruby-openid-2.1.4/lib/openid/yadis/xrds.rb
new file mode 100644 (file)
index 0000000..0ebf34a
--- /dev/null
@@ -0,0 +1,155 @@
+require 'rexml/document'
+require 'rexml/element'
+require 'rexml/xpath'
+
+require 'openid/yadis/xri'
+
+module OpenID
+  module Yadis
+
+    XRD_NS_2_0 = 'xri://$xrd*($v*2.0)'
+    XRDS_NS = 'xri://$xrds'
+
+    XRDS_NAMESPACES = {
+      'xrds' => XRDS_NS,
+      'xrd' => XRD_NS_2_0,
+    }
+
+    class XRDSError < StandardError; end
+
+    # Raised when there's an assertion in the XRDS that it does not
+    # have the authority to make.
+    class XRDSFraud < XRDSError
+    end
+
+    def Yadis::get_canonical_id(iname, xrd_tree)
+      # Return the CanonicalID from this XRDS document.
+      #
+      # @param iname: the XRI being resolved.
+      # @type iname: unicode
+      #
+      # @param xrd_tree: The XRDS output from the resolver.
+      #
+      # @returns: The XRI CanonicalID or None.
+      # @returntype: unicode or None
+
+      xrd_list = []
+      REXML::XPath::match(xrd_tree.root, '/xrds:XRDS/xrd:XRD', XRDS_NAMESPACES).each { |el|
+        xrd_list << el
+      }
+
+      xrd_list.reverse!
+
+      cid_elements = []
+
+      if !xrd_list.empty?
+        xrd_list[0].elements.each { |e|
+          if !e.respond_to?('name')
+            next
+          end
+          if e.name == 'CanonicalID'
+            cid_elements << e
+          end
+        }
+      end
+
+      cid_element = cid_elements[0]
+
+      if !cid_element
+        return nil
+      end
+
+      canonicalID = XRI.make_xri(cid_element.text)
+
+      childID = canonicalID.downcase
+
+      xrd_list[1..-1].each { |xrd|
+        parent_sought = childID[0...childID.rindex('!')]
+
+        parent = XRI.make_xri(xrd.elements["CanonicalID"].text)
+
+        if parent_sought != parent.downcase
+          raise XRDSFraud.new(sprintf("%s can not come from %s", parent_sought,
+                                      parent))
+        end
+
+        childID = parent_sought
+      }
+
+      root = XRI.root_authority(iname)
+      if not XRI.provider_is_authoritative(root, childID)
+        raise XRDSFraud.new(sprintf("%s can not come from root %s", childID, root))
+      end
+
+      return canonicalID
+    end
+
+    class XRDSError < StandardError
+    end
+
+    def Yadis::parseXRDS(text)
+      if text.nil?
+        raise XRDSError.new("Not an XRDS document.")
+      end
+
+      begin
+        d = REXML::Document.new(text)
+      rescue RuntimeError => why
+        raise XRDSError.new("Not an XRDS document. Failed to parse XML.")
+      end
+
+      if is_xrds?(d)
+        return d
+      else
+        raise XRDSError.new("Not an XRDS document.")
+      end
+    end
+
+    def Yadis::is_xrds?(xrds_tree)
+      xrds_root = xrds_tree.root
+      return (!xrds_root.nil? and
+        xrds_root.name == 'XRDS' and
+        xrds_root.namespace == XRDS_NS)
+    end
+
+    def Yadis::get_yadis_xrd(xrds_tree)
+      REXML::XPath.each(xrds_tree.root,
+                        '/xrds:XRDS/xrd:XRD[last()]',
+                        XRDS_NAMESPACES) { |el|
+        return el
+      }
+      raise XRDSError.new("No XRD element found.")
+    end
+
+    # aka iterServices in Python
+    def Yadis::each_service(xrds_tree, &block)
+      xrd = get_yadis_xrd(xrds_tree)
+      xrd.each_element('Service', &block)
+    end
+
+    def Yadis::services(xrds_tree)
+      s = []
+      each_service(xrds_tree) { |service|
+        s << service
+      }
+      return s
+    end
+
+    def Yadis::expand_service(service_element)
+      es = service_element.elements
+      uris = es.each('URI') { |u| }
+      uris = prio_sort(uris)
+      types = es.each('Type/text()')
+      # REXML::Text objects are not strings.
+      types = types.collect { |t| t.to_s }
+      uris.collect { |uri| [types, uri.text, service_element] }
+    end
+
+    # Sort a list of elements that have priority attributes.
+    def Yadis::prio_sort(elements)
+      elements.sort { |a,b|
+        a.attribute('priority').to_s.to_i <=> b.attribute('priority').to_s.to_i
+      }
+    end
+  end
+end
diff --git a/vendor/gems/ruby-openid-2.1.4/lib/openid/yadis/xri.rb b/vendor/gems/ruby-openid-2.1.4/lib/openid/yadis/xri.rb
new file mode 100644 (file)
index 0000000..89dd99a
--- /dev/null
@@ -0,0 +1,90 @@
+require 'openid/yadis/xrds'
+require 'openid/fetchers'
+
+module OpenID
+  module Yadis
+    module XRI
+
+      # The '(' is for cross-reference authorities, and hopefully has a
+      # matching ')' somewhere.
+      XRI_AUTHORITIES = ["!", "=", "@", "+", "$", "("]
+
+      def self.identifier_scheme(identifier)
+        if (!identifier.nil? and
+            identifier.length > 0 and
+            (identifier.match('^xri://') or
+             XRI_AUTHORITIES.member?(identifier[0].chr)))
+          return :xri
+        else
+          return :uri
+        end
+      end
+
+      # Transform an XRI reference to an IRI reference.  Note this is
+      # not not idempotent, so do not apply this to an identifier more
+      # than once.  XRI Syntax section 2.3.1
+      def self.to_iri_normal(xri)
+        iri = xri.dup
+        iri.insert(0, 'xri://') if not iri.match('^xri://')
+        return escape_for_iri(iri)
+      end
+
+      # Note this is not not idempotent, so do not apply this more than
+      # once.  XRI Syntax section 2.3.2
+      def self.escape_for_iri(xri)
+        esc = xri.dup
+        # encode all %
+        esc.gsub!(/%/, '%25')
+        esc.gsub!(/\((.*?)\)/) { |xref_match|
+          xref_match.gsub(/[\/\?\#]/) { |char_match|
+            CGI::escape(char_match)
+          }
+        }
+        return esc
+      end
+
+      # Transform an XRI reference to a URI reference.  Note this is not
+      # not idempotent, so do not apply this to an identifier more than
+      # once.  XRI Syntax section 2.3.1
+      def self.to_uri_normal(xri)
+        return iri_to_uri(to_iri_normal(xri))
+      end
+
+      # RFC 3987 section 3.1
+      def self.iri_to_uri(iri)
+        uri = iri.dup
+        # for char in ucschar or iprivate
+        # convert each char to %HH%HH%HH (as many %HH as octets)
+        return uri
+      end
+
+      def self.provider_is_authoritative(provider_id, canonical_id)
+        lastbang = canonical_id.rindex('!')
+        return false unless lastbang
+        parent = canonical_id[0...lastbang]
+        return parent == provider_id
+      end
+
+      def self.root_authority(xri)
+        xri = xri[6..-1] if xri.index('xri://') == 0
+        authority = xri.split('/', 2)[0]
+        if authority[0].chr == '('
+          root = authority[0...authority.index(')')+1]
+        elsif XRI_AUTHORITIES.member?(authority[0].chr)
+          root = authority[0].chr
+        else
+          root = authority.split(/[!*]/)[0]
+        end
+
+        self.make_xri(root)
+      end
+
+      def self.make_xri(xri)
+        if xri.index('xri://') != 0
+          xri = 'xri://' + xri
+        end
+        return xri
+      end
+    end
+  end
+end
diff --git a/vendor/gems/ruby-openid-2.1.4/lib/openid/yadis/xrires.rb b/vendor/gems/ruby-openid-2.1.4/lib/openid/yadis/xrires.rb
new file mode 100644 (file)
index 0000000..3943911
--- /dev/null
@@ -0,0 +1,106 @@
+require "cgi"
+require "openid/yadis/xri"
+require "openid/yadis/xrds"
+require "openid/fetchers"
+
+module OpenID
+
+  module Yadis
+
+    module XRI
+
+      class XRIHTTPError < StandardError; end
+
+      class ProxyResolver
+
+        DEFAULT_PROXY = 'http://proxy.xri.net/'
+
+        def initialize(proxy_url=nil)
+          if proxy_url
+            @proxy_url = proxy_url
+          else
+            @proxy_url = DEFAULT_PROXY
+          end
+
+          @proxy_url += '/' unless @proxy_url.match('/$')
+        end
+
+        def query_url(xri, service_type=nil)
+          # URI normal form has a leading xri://, but we need to strip
+          # that off again for the QXRI.  This is under discussion for
+          # XRI Resolution WD 11.
+          qxri = XRI.to_uri_normal(xri)[6..-1]
+          hxri = @proxy_url + qxri
+          args = {'_xrd_r' => 'application/xrds+xml'}
+          if service_type
+            args['_xrd_t'] = service_type
+          else
+            # don't perform service endpoint selection
+            args['_xrd_r'] += ';sep=false'
+          end
+
+          return XRI.append_args(hxri, args)
+        end
+
+        def query(xri, service_types)
+          # these can be query args or http headers, needn't be both.
+          # headers = {'Accept' => 'application/xrds+xml;sep=true'}
+          canonicalID = nil
+
+          services = service_types.collect { |service_type|
+            url = self.query_url(xri, service_type)
+            begin
+              response = OpenID.fetch(url)
+            rescue
+              raise XRIHTTPError, ["Could not fetch #{xri}", $!]
+            end
+            raise XRIHTTPError, "Could not fetch #{xri}" if response.nil?
+
+            xrds = Yadis::parseXRDS(response.body)
+            canonicalID = Yadis::get_canonical_id(xri, xrds)
+
+            Yadis::services(xrds) unless xrds.nil?
+          }
+          # TODO:
+          #  * If we do get hits for multiple service_types, we're almost
+          #    certainly going to have duplicated service entries and
+          #    broken priority ordering.
+          services = services.inject([]) { |flatter, some_services|
+            flatter += some_services unless some_services.nil?
+          }
+
+          return canonicalID, services
+        end
+      end
+
+      def self.urlencode(args)
+        a = []
+        args.each do |key, val|
+          a << (CGI::escape(key) + "=" + CGI::escape(val))
+        end
+        a.join("&")
+      end
+
+      def self.append_args(url, args)
+        return url if args.length == 0
+
+        # rstrip question marks
+        rstripped = url.dup
+        while rstripped[-1].chr == '?'
+          rstripped = rstripped[0...rstripped.length-1]
+        end
+
+        if rstripped.index('?')
+          sep = '&'
+        else
+          sep = '?'
+        end
+
+        return url + sep + XRI.urlencode(args)
+      end
+
+    end
+
+  end
+
+end
diff --git a/vendor/gems/ruby-openid-2.1.4/test/data/accept.txt b/vendor/gems/ruby-openid-2.1.4/test/data/accept.txt
new file mode 100644 (file)
index 0000000..884ff66
--- /dev/null
@@ -0,0 +1,124 @@
+# Accept: [Accept: header value from RFC2616,
+#     http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html]
+# Available: [whitespace-separated content types]
+# Expected: [Accept-header like list, containing the available content
+#     types with their q-values]
+
+Accept: */*
+Available: text/plain
+Expected: text/plain; q=1.0
+
+Accept: */*
+Available: text/plain, text/html
+Expected: text/plain; q=1.0, text/html; q=1.0
+
+# The order matters
+Accept: */*
+Available: text/html, text/plain
+Expected: text/html; q=1.0, text/plain; q=1.0
+
+Accept: text/*, */*; q=0.9
+Available: text/plain, image/jpeg
+Expected: text/plain; q=1.0, image/jpeg; q=0.9
+
+Accept: text/*, */*; q=0.9
+Available: image/jpeg, text/plain
+Expected: text/plain; q=1.0, image/jpeg; q=0.9
+
+# wildcard subtypes still reject differing main types
+Accept: text/*
+Available: image/jpeg, text/plain
+Expected: text/plain; q=1.0
+
+Accept: text/html
+Available: text/html
+Expected: text/html; q=1.0
+
+Accept: text/html, text/*
+Available: text/html
+Expected: text/html; q=1.0
+
+Accept: text/html, text/*
+Available: text/plain, text/html
+Expected: text/plain; q=1.0, text/html; q=1.0
+
+Accept: text/html, text/*; q=0.9
+Available: text/plain, text/html
+Expected: text/html; q=1.0, text/plain; q=0.9
+
+# If a more specific type has a higher q-value, then the higher value wins
+Accept: text/*; q=0.9, text/html
+Available: text/plain, text/html
+Expected: text/html; q=1.0, text/plain; q=0.9
+
+Accept: */*, text/*; q=0.9, text/html; q=0.1
+Available: text/plain, text/html, image/monkeys
+Expected: image/monkeys; q=1.0, text/plain; q=0.9, text/html; q=0.1
+
+Accept: text/*, text/html; q=0
+Available: text/html
+Expected:
+
+Accept: text/*, text/html; q=0
+Available: text/html, text/plain
+Expected: text/plain; q=1.0
+
+Accept: text/html
+Available: text/plain
+Expected:
+
+Accept: application/xrds+xml, text/html; q=0.9
+Available: application/xrds+xml, text/html
+Expected: application/xrds+xml; q=1.0, text/html; q=0.9
+
+Accept: application/xrds+xml, */*; q=0.9
+Available: application/xrds+xml, text/html
+Expected: application/xrds+xml; q=1.0, text/html; q=0.9
+
+Accept: application/xrds+xml, application/xhtml+xml; q=0.9, text/html; q=0.8, text/xml; q=0.7
+Available: application/xrds+xml, text/html
+Expected: application/xrds+xml; q=1.0, text/html; q=0.8
+
+# See http://www.rfc-editor.org/rfc/rfc3023.txt, section A.13
+Accept: application/xrds
+Available: application/xrds+xml
+Expected:
+
+Accept: application/xrds+xml
+Available: application/xrds
+Expected:
+
+Accept: application/xml
+Available: application/xrds+xml
+Expected:
+
+Available: application/xrds+xml
+Accept: application/xml
+Expected:
+
+Available:
+Accept: not_a_content_type
+Expected:
+
+Available: text/html
+Accept: not_a_content_type, text/html
+Expected: text/html; q=1.0
+
+#################################################
+# The tests below this line are documentation of how this library
+# works. If the implementation changes, it's acceptable to change the
+# test to reflect that. These are specified so that we can make sure
+# that the current implementation actually works the way that we
+# expect it to given these inputs.
+
+Accept: text/html;level=1
+Available: text/html
+Expected: text/html; q=1.0
+
+Accept: text/html; level=1, text/html; level=9; q=0.1
+Available: text/html
+Expected: text/html; q=1.0
+
+Accept: text/html; level=9; q=0.1, text/html; level=1
+Available: text/html
+Expected: text/html; q=1.0
diff --git a/vendor/gems/ruby-openid-2.1.4/test/data/dh.txt b/vendor/gems/ruby-openid-2.1.4/test/data/dh.txt
new file mode 100644 (file)
index 0000000..0fa5231
--- /dev/null
@@ -0,0 +1,29 @@
+130706940119084053627151828062879423433929180135817317038378606310097533503449582079984816816837125851552273641820339909167103200910805078308128174143174269944095368580519322913514764528012639683546377014716235962867583443566164615728897857285824741767070432119909660645255499710701356135207437699643611094585 139808169914464096465921128085565621767096724855516655439365028496569658038844954238931647642811548254956660405394116677296461848124300258439895306367561416289126854788101396379292925819850897858045772500578222021901631436550118958972312221974009238050517034542286574826081826542722270952769078386418682059418
+91966407878983240112417790733941098492087186469785726449910011271065622315680646030230288265496017310433513856308693810812043160919214636748486185212617634222158204354206411031403206076739932806412551605172319515223573351072757800448643935018534945933808900467686115619932664888581913179496050117713298715475 88086484332488517006277516020842172054013692832175783214603951240851750819999098631851571207693874357651112736088114133607400684776234181681933311972926752846692615822043533641407510569745606256772455614745111122033229877596984718963046218854103292937700694160593653595134512369959987897086639788909618660591
+94633950701209990078055218830969910271587805983595045023718108184189787131629772007048606080263109446462048743696369276578815611098215686598630889831104860221067872883514840819381234786050098278403321905311637820524177879167250981289318356078312300538871435101338967079907049912435983871847334104247675360099 136836393035803488129856151345450008294260680733328546556640578838845312279198933806383329293483852515700876505956362639881210101974254765087350842271260064592406308509078284840473735904755203614987286456952991025347168970462354352741159076541157478949094536405618626397435745496863324654768971213730622037771
+24685127248019769965088146297942173464487677364928435784091685260262292485380918213538979925891771204729738138857126454465630594391449913947358655368215901119137728648638547728497517587701248406019427282237279437409508871300675355166059811431191200555457304463617727969228965042729205402243355816702436970430 103488011917988946858248200111251786178288940265978921633592888293430082248387786443813155999158786903216094876295371112716734481877806417714913656921169196196571699893360825510307056269738593971532017994987406325068886420548597161498019372380511676314312298122272401348856314619382867707981701472607230523868
+116791045850880292989786005885944774698035781824784400772676299590038746153860847252706167458966356897309533614849402276819438194497464696186624618374179812548893947178936305721131565012344462048549467883494038577857638815386798694225798517783768606048713198211730870155881426709644960689953998714045816205549 25767875422998856261320430397505398614439586659207416236135894343577952114994718158163212134503751463610021489053571733974769536157057815413209619147486931502025658987681202196476489081257777148377685478756033509708349637895740799542063593586769082830323796978935454479273531157121440998804334199442003857410
+75582226959658406842894734694860761896800153014775231713388264961517169436476322183886891849966756849783437334069692683523296295601533803799559985845105706728538458624387103621364117548643541824878550074680443708148686601108223917493525070861593238005735446708555769966855130921562955491250908613793521520082 51100990616369611694975829054222013346248289055987940844427061856603230021472379888102172458517294080775792439385531234808129302064303666640376750139242970123503857186428797403843206765926798353022284672682073397573130625177187185114726049347844460311761033584101482859992951420083621362870301150543916815123
+22852401165908224137274273646590366934616265607879280260563022941455466297431255072303172649495519837876946233272420969249841381161312477263365567831938496555136366981954001163034914812189448922853839616662859772087929140818377228980710884492996109434435597500854043325062122184466315338260530734979159890875 35017410720028595029711778101507729481023945551700945988329114663345341120595162378885287946069695772429641825579528116641336456773227542256911497084242947904528367986325800537695079726856460817606404224094336361853766354225558025931211551975334149258299477750615397616908655079967952372222383056221992235704
+37364490883518159794654045194678325635036705086417851509136183713863262621334636905291385255662750747808690129471989906644041585863034419130023070856805511017402434123099100618568335168939301014148587149578150068910141065808373976114927339040964292334109797421173369274978107389084873550233108940239410902552 40916262212189137562350357241447034318002130016858244002788189310078477605649010031339865625243230798681216437501833540185827501244378529230150467789369234869122179247196276164931090039290879808162629109742198951942358028123056268054775108592325500609335947248599688175189333996086475013450537086042387719925
+42030470670714872936404499074069849778147578537708230270030877866700844337372497704027708080369726758812896818567830863540507961487472657570488625639077418109017434494794778542739932765561706796300920251933107517954265066804108669800167526425723377411855061131982689717887180411017924173629124764378241885274 124652439272864857598747946875599560379786580730218192165733924418687522301721706620565030507816884907589477351553268146177293719586287258662025940181301472851649975563004543250656807255226609296537922304346339513054316391667044301386950180277940536542183725690479451746977789001659540839582630251935163344393
+33176766914206542084736303652243484580303865879984981189372762326078776390896986743451688462101732968104375838228070296418541745483112261133079756514082093269959937647525005374035326747696591842313517634077723301677759648869372517403529488493581781546743147639937580084065663597330159470577639629864369972900 67485835091897238609131069363014775606263390149204621594445803179810038685760826651889895397414961195533694176706808504447269558421955735607423135937153901140512527504198912146656610630396284977496295289999655140295415981288181545277299615922576281262872097567020980675200178329219970170480653040350512964539
+131497983897702298481056962402569646971797912524360547236788650961059980711719600424210346263081838703940277066368168874781981151411096949736205282734026497995296147418292226818536168555712128736975034272678008697869326747592750850184857659420541708058277866000692785617873742438060271311159568468507825422571 5400380840349873337222394910303409203226429752629134721503171858543984393161548520471799318518954232197106728096866840965784563043721652790856860155702760027304915133166173298206604451826182024471262142046935060360564569939062438160049193241369468208458085699995573492688298015026628427440418009025072261296
+83265103005695640943261961853521077357830295830250157593141844209296716788437615940096402365505416686459260302419338241462783388722843946886845478224048360927114533590583464979009731440049610985062455108831881153988321298531365779084012803908832525921630534096740755274371500276660832724874701671184539131864 141285570207910287798371174771658911045525474449663877845558585668334618068814605961306961485855329182957174312715910923324965889174835444049526313968571611940626279733302104955951067959291852710640374412577070764165811275030632465290729619533330733368808295932659463215921521905553936914975786500018720073003
+68435028583616495789148116911096163791710022987677894923742899873596891423986951658100606742052014161171185231735413902875605720814417622409817842932759492013585936536452615480700628719795872201528559780249210820284350401473564919576289210869896327937002173624497942136329576506818749730506884927872345019446 134655528287263100540003157571441260698452262106680191153945271167894435782028803135774578949200580551016388918860856991026082917835209212892423567114480975540305860034439015788120390011692862968771136814777768281366591257663821495720134621172848947971117885754539770645621669309650476331439675400544167728223
+97765390064836080322590528352647421920257073063706996347334558390461274981996865736612531330863478931481491964338380362350271734683183807511097331539820133036984271653285063355715726806139083282458695728902452215405696318402583540317419929113959816258829534543044153959951908676300847164682178008704099351835 92552521881196975294401505656851872247567784546370503402756239533783651371688190302773864319828182042605239246779598629409815474038541272600580320815319709309111399294952620375093803971373108792300726524826209329889463854451846561437729676142864421966497641824498079067929811613947148353921163336822026640804
+145767094672933012300753301037546647564595762930138884463767054235112032706630891961371504668013023047595721138624016493638510710257541241706724342585654715468628355455898091951826598092812212209834746162089753649871544789379424903025374228231365026585872808685759231756517703720396301355299998059523896918448 116669462839999965355861187716880953863237226719689755457884414384663576662696981997535568446560375442532084973721539944428004043491468494548231348032618218312515409944970197902589794303562379864012797605284844016184274353252071642511293089390472576498394410829972525726474727579603392265177009323768966538608
+34172517877854802711907683049441723730724885305592620486269966708379625109832852005775048584124451699198484092407720344962116726808090368739361658889584507734617844212547181476646725256303630128954338675520938806905779837227983648887192531356390902975904503218654196581612781227843742951241442641220856414232 126013077261793777773236390821108423367648447987653714614732477073177878509574051196587476846560696305938891953527959347566502332765820074506907037627115954790645652211088723122982633069089920979477728376746424256704724173255656757918995039125823421607024407307091796807227896314403153380323770001854211384322
+9979624731056222925878866378063961280844793874828281622845276060532093809300121084179730782833657205171434732875093693074415298975346410131191865198158876447591891117577190438695367929923494177555818480377241891190442070100052523008290671797937772993634966511431668500154258765510857129203107386972819651767 76559085024395996164590986654274454741199399364851956129137304209855150918182685643729981600389513229011956888957763987167398150792454613751473654448162776379362213885827651020309844507723069713820393068520302223477225569348080362344052033711960892643036147232270133731530049660264526964146237693063093765111
+18162696663677410793062235946366423954875282212790518677684260521370996677183041664345920941714064628111537529793170736292618705900247450994864220481135611781148410617609559050220262121494712903009168783279356915189941268264177631458029177102542745167475619936272581126346266816618866806564180995726437177435 63244550218824945129624987597134280916829928261688093445040235408899092619821698537312158783367974202557699994650667088974727356690181336666077506063310290098995215324552449858513870629176838494348632073938023916155113126203791709810160925798130199717340478393420816876665127594623142175853115698049952126277
+4817943161362708117912118300716778687157593557807116683477307391846133734701449509121209661982298574607233039490570567781316652698287671086985501523197566560479906850423709894582834963398034434055472063156147829131181965140631257939036683622084290629927807369457311894970308590034407761706800045378158588657 61612160237840981966750225147965256022861527286827877531373888434780789812764688703260066154973576040405676432586962624922734102370509771313805122788566405984830112657060375568510809122230960988304085950306616401218206390412815884549481965750553137717475620505076144744211331973240555181377832337912951699135
+36363324947629373144612372870171042343590861026293829791335153646774927623889458346817049419803031378037141773848560341251355283891019532059644644509836766167835557471311319194033709837770615526356168418160386395260066262292757953919140150454538786106958252854181965875293629955562111756775391296856504912587 86831561031659073326747216166881733513938228972332631084118628692228329095617884068498116676787029033973607066377816508795286358748076949738854520048303930186595481606562375516134920902325649683618195251332651685732712539073110524182134321873838204219194459231650917098791250048469346563303077080880339797744
+26406869969418301728540993821409753036653370247174689204659006239823766914991146853283367848649039747728229875444327879875275718711878211919734397349994000106499628652960403076186651083084423734034070082770589453774926850920776427074440483233447839259180467805375782600203654373428926653730090468535611335253 100139935381469543084506312717977196291289016554846164338908226931204624582010530255955411615528804421371905642197394534614355186795223905217732992497673429554618838376065777445760355552020655667172127543653684405493978325270279321013143828897100500212200358450649158287605846102419527584313353072518101626851
+92613116984760565837109105383781193800503303131143575169488835702472221039082994091847595094556327985517286288659598094631489552181233202387028607421487026032402972597880028640156629614572656967808446397456622178472130864873587747608262139844319805074476178618930354824943672367046477408898479503054125369731 30023391082615178562263328892343821010986429338255434046051061316154579824472412477397496718186615690433045030046315908170615910505869972621853946234911296439134838951047107272129711854649412919542407760508235711897489847951451200722151978578883748353566191421685659370090024401368356823252748749449302536931
+31485815361342085113278193504381994806529237123359718043079410511224607873725611862217941085749929342777366642477711445011074784469367917758629403998067347054115844421430072631339788256386509261291675080191633908849638316409182455648806133048549359800886124554879661473112614246869101243501787363247762961784 114503770698890543429251666713050844656853278831559195214556474458830029271801818536133531843456707474500106283648085144619097572354066554819887152106174400667929098257361286338795493838820850475790977445807435511982704395422526800272723708548541616513134676140304653112325071112865020365664833601046215694089
+76882090884790547431641385530818076533805072109483843307806375918023300052767710853172670987385376253156912268523505310624133905633437815297307463917718596711590885553760690350221265675690787249135345226947453988081566088302642706234126002514517416493192624887800567412565527886687096028028124049522890448168 15056463217273240496622619354104573042767532856243223052125822509781815362480522535564283485059790932505429110157271454207173426525345813426696743168079246510944969446574354255284952839036431873039487144279164893710061580467579842173706653409487110282515691099753380094215805485573768509475850463001549608836
+52345178981230648108672997265819959243255047568833938156267924185186047373470984278294897653277996726416846430969793375429223610099546622112048283560483136389901514170116723365811871938630317974150540909650396429631704968748113009366339718498979597226137532343384889080245796447593572468846438769413505393967 32148494517199936472358017244372701214529606506776255341152991328091526865643069587953759877295255050519124541457805199596762210567333445908166076384465183589342153762720515477404466193879418014196727238972417616122646440870364200208488239778452378059236162633837824948613596114768455832408342040970780086
+41095268619128788015767564971105114602454449306041732792746397800275041704886345704294273937217484580365505320134717320083763349380629342859670693445658118959823430378844830923452105707338162448974869312012791385772125813291388247857971218575518319578818336960572244046567099555399203328678654466958536663208 92166550199033418923713824997841892577149715275633481076285269142670107687867024550593869464613175882141630640739938334001211714884975032600306279287443909448541179109981755796752132502127330056736913454039526413284519137059580845856736918773597087836203497066909257930043736166431682872083389105176299181629
+40049143661018504441607875135884755310012910557581028447435354354754245291878800571089144452035026644953322330676651798951447670184106450649737772686119714700743396359069052813433030118630105307022867200053964644574786137276428546712005171080129190959914708907200288299169344380390093918556722227705114244981 108159089972386282154772900619022507336076619354549601813179459338897131937353741544606392560724999980281424266891537298473163753022749859939445293926707568015958367188089915420630082556748668489756475027008449860889202622698060097015044886961901650857610841562477736791450080980702347705778074391774667412741
+69905259478181995876884927656894491893594530150260951315109404530530357998889589977208787140430938039028941393673520799460431992051993157468616168400324834880926190141581037597526917869362292931957289043707855837933490285814769110495657056206391880865972389421774822461752702336812585852278453803972600333734 71821415380277072313878763768684432371552628204186742842154591000123020597011744840460964835414360968627162765288463383113375595799297552681618876474019263288277398833725479226930770694271622605114061622753165584075733358178384410640349907375170170910499615355511313349300918885560131539570707695789106185664
+26945345439378873515011714350080059082081595419023056538696949766471272811362104837806324694947413603019863785876836706911406330379274553386254346050697348395574746891556054334903838949157798006141473389066020212044825140294048709654273698482867946522782450500680195477050110145664069582549935651920545151500 80313315938584480048642653013876614091607852535582224914294013785054094052454758327935781971746329853786568549510067442145637007308960551652864942042189241081946607011847245280773379099020221884296226818685556430275385068764313042226925852500883894269809033380734632866477789520106865758504064806906234130588
diff --git a/vendor/gems/ruby-openid-2.1.4/test/data/example-xrds.xml b/vendor/gems/ruby-openid-2.1.4/test/data/example-xrds.xml
new file mode 100644 (file)
index 0000000..101ba3b
--- /dev/null
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Sample XRDS file at: NAME -->
+<xrds:XRDS
+    xmlns:xrds="xri://$xrds"
+    xmlns="xri://$xrd*($v*2.0)">
+  <XRD>
+
+    <Service priority="0">
+      <Type>http://example.com/</Type>
+      <URI>http://www.openidenabled.com/</URI>
+    </Service>
+
+  </XRD>
+</xrds:XRDS>
diff --git a/vendor/gems/ruby-openid-2.1.4/test/data/linkparse.txt b/vendor/gems/ruby-openid-2.1.4/test/data/linkparse.txt
new file mode 100644 (file)
index 0000000..2fa38cd
--- /dev/null
@@ -0,0 +1,587 @@
+Num Tests: 72
+
+OpenID link parsing test cases
+Copyright (C) 2005-2008, JanRain, Inc.
+See COPYING for license information.
+
+File format
+-----------
+
+All text before the first triple-newline (this chunk) should be ignored.
+
+This file may be interpreted as Latin-1 or UTF-8.
+
+Test cases separated by three line separators (`\n\n\n'). The test
+cases consist of a headers section followed by a data block. These are
+separated by a double newline. The headers consist of the header name,
+followed by a colon, a space, the value, and a newline. There must be
+one, and only one, `Name' header for a test case. There may be zero or
+more link headers. The `Link' header consists of whitespace-separated
+attribute pairs. A link header with an empty string as a value
+indicates an empty but present link tag. The attribute pairs are `='
+separated and not quoted.
+
+Optional Links and attributes have a trailing `*'. A compilant
+implementation may produce this as output or may not. A compliant
+implementation will not produce any output that is absent from this
+file.
+
+
+Name: No link tag at all
+
+<html>
+<head>
+</head>
+</html>
+
+
+Name: Link element first
+
+<link>
+
+
+Name: Link inside HTML, not head
+
+<html>
+<link>
+
+
+Name: Link inside head, not html
+
+<head>
+<link>
+
+
+Name: Link inside html, after head
+
+<html>
+<head>
+</head>
+<link>
+
+
+Name: Link inside html, before head
+
+<html>
+<link>
+<head>
+
+
+Name: Link before html and head
+
+<link>
+<html>
+<head>
+
+
+Name: Link after html document with head
+
+<html>
+<head>
+</head>
+</html>
+<link>
+
+
+Name: Link inside html inside head, inside another html
+
+<html>
+<head>
+<html>
+<link>
+
+
+Name: Link inside html inside head
+
+<head>
+<html>
+<link>
+
+
+Name: link inside body inside head inside html
+
+<html>
+<head>
+<body>
+<link>
+
+
+Name: Link inside head inside head inside html
+
+<html>
+<head>
+<head>
+<link>
+
+
+Name: Link inside script inside head inside html
+
+<html>
+<head>
+<script>
+<link>
+</script>
+
+
+Name: Link inside comment inside head inside html
+
+<html>
+<head/>
+<link>
+
+
+Name: Link inside of head after short head
+
+<html>
+<head/>
+<head>
+<link>
+
+
+Name: Plain vanilla
+Link:
+
+<html>
+<head>
+<link>
+
+
+Name: Ignore tags in the <script:... > namespace
+Link*:
+
+<html>
+<head>
+<script:paddypan>
+<link>
+</script:paddypan>
+
+
+Name: Short link tag
+Link:
+
+<html>
+<head>
+<link/>
+
+
+Name: Spaces in the HTML tag
+Link:
+
+<html >
+<head>
+<link>
+
+
+Name: Spaces in the head tag
+Link:
+
+<html>
+<head >
+<link>
+
+
+Name: Spaces in the link tag
+Link:
+
+<html>
+<head>
+<link >
+
+
+Name: No whitespace
+Link:
+
+<html><head><link>
+
+
+Name: Closed head tag
+Link:
+
+<html>
+<head>
+<link>
+</head>
+
+
+Name: One good, one bad (after close head)
+Link:
+
+<html>
+<head>
+<link>
+</head>
+<link>
+
+
+Name: One good, one bad (after open body)
+Link:
+
+<html>
+<head>
+<link>
+<body>
+<link>
+
+
+Name: ill formed (missing close head)
+Link:
+
+<html>
+<head>
+<link>
+</html>
+
+
+Name: Ill formed (no close head, link after </html>)
+Link:
+
+<html>
+<head>
+<link>
+</html>
+<link>
+
+
+Name: Ignore random tags inside of html
+Link:
+
+<html>
+<delicata>
+<head>
+<title>
+<link>
+
+
+Name: case-folding
+Link*:
+
+<HtMl>
+<hEaD>
+<LiNk>
+
+
+Name: unexpected tags
+Link:
+
+<butternut>
+<html>
+<summer>
+<head>
+<turban>
+<link>
+
+
+Name: un-closed script tags
+Link*:
+
+<html>
+<head>
+<script>
+<link>
+
+
+Name: un-closed script tags (no whitespace)
+Link*:
+
+<html><head><script><link>
+
+
+Name: un-closed comment
+Link*:
+
+<html>
+<head>
+<!--
+<link>
+
+
+Name: un-closed CDATA
+Link*:
+
+<html>
+<head>
+<![CDATA[
+<link>
+
+
+Name: cdata-like
+Link*:
+
+<html>
+<head>
+<![ACORN[
+<link>
+]]>
+
+
+Name: comment close only
+Link:
+
+<html>
+<head>
+<link>
+-->
+
+
+Name: Vanilla, two links
+Link:
+Link:
+
+<html>
+<head>
+<link>
+<link>
+
+
+Name: extra tag, two links
+Link:
+Link:
+
+<html>
+<gold nugget>
+<head>
+<link>
+<link>
+
+
+Name: case-fold, body ends, two links
+Link:
+Link*:
+
+<html>
+<head>
+<link>
+<LiNk>
+<body>
+<link>
+
+
+Name: simple, non-quoted rel
+Link: rel=openid.server
+
+<html><head><link rel=openid.server>
+
+
+Name: short tag has rel
+Link: rel=openid.server
+
+<html><head><link rel=openid.server/>
+
+
+Name: short tag w/space has rel
+Link: rel=openid.server
+
+<html><head><link rel=openid.server />
+
+
+Name: extra non-attribute, has rel
+Link: rel=openid.server hubbard*=hubbard
+
+<html><head><link hubbard rel=openid.server>
+
+
+Name: non-attr, has rel, short
+Link: rel=openid.server hubbard*=hubbard
+
+<html><head><link hubbard rel=openid.server/>
+
+
+Name: non-attr, has rel, short, space
+Link: rel=openid.server hubbard*=hubbard
+
+<html><head><link hubbard rel=openid.server />
+
+
+Name: misplaced slash has rel
+Link: rel=openid.server
+
+<html><head><link / rel=openid.server>
+
+
+Name: quoted rel
+Link: rel=openid.server
+
+<html><head><link rel="openid.server">
+
+
+Name: single-quoted rel
+Link: rel=openid.server
+
+<html><head><link rel='openid.server'>
+
+
+Name: two links w/ rel
+Link: x=y
+Link: a=b
+
+<html><head><link x=y><link a=b>
+
+
+Name: non-entity
+Link: x=&y
+
+<html><head><link x=&y>
+
+
+Name: quoted non-entity
+Link: x=&y
+
+<html><head><link x="&y">
+
+
+Name: quoted entity
+Link: x=&
+
+<html><head><link x="&amp;">
+
+
+Name: entity not processed
+Link: x=&#26;
+
+<html><head><link x="&#26;">
+
+
+Name: &lt;
+Link: x=<
+
+<html><head><link x="&lt;">
+
+
+Name: &gt;
+Link: x=>
+
+<html><head><link x="&gt;">
+
+
+Name: &quot;
+Link: x="
+
+<html><head><link x="&quot;">
+
+
+Name: &amp;&quot;
+Link: x=&"
+
+<html><head><link x="&amp;&quot;">
+
+
+Name: mixed entity and non-entity
+Link: x=&"&hellip;>
+
+<html><head><link x="&amp;&quot;&hellip;&gt;">
+
+
+Name: mixed entity and non-entity (w/normal chars)
+Link: x=x&"&hellip;>x
+
+<html><head><link x="x&amp;&quot;&hellip;&gt;x">
+
+
+Name: broken tags
+Link*: x=y
+Link*: x=y<
+
+<html><head><link x=y<>
+
+
+Name: missing close pointy
+Link*: x=y
+Link*: x=y<link z=y
+Link*: z=y
+
+<html><head><link x=y<link z=y />
+
+
+Name: missing attribute value
+Link: x=y y*=y
+Link: x=y
+
+<html><head><link x=y y=><link x=y />
+
+
+Name: Missing close pointy (no following)
+Link*: x=y
+
+<html><head><link x=y
+
+
+Name: Should be quoted
+Link*: x=<
+
+<html><head><link x="<">
+
+
+Name: Should be quoted (2)
+Link*: x=>
+Link*: x=x
+
+<html><head><link x=">">
+
+
+Name: Repeated attribute
+Link: x=y
+
+<html><head><link x=z x=y>
+
+
+Name: Repeated attribute (2)
+Link: x=y
+
+<html><head><link x=y x=y>
+
+
+Name: Two attributes
+Link: x=y y=z
+
+<html><head><link x=y y=z>
+
+
+Name: Well-formed link rel="openid.server"
+Link: rel=openid.server href=http://www.myopenid.com/server
+
+<html>
+  <head>
+    <link rel="openid.server"
+          href="http://www.myopenid.com/server" />
+  </head>
+</html>
+
+
+Name: Well-formed link rel="openid.server" and "openid.delegate"
+Link: rel=openid.server href=http://www.myopenid.com/server
+Link: rel=openid.delegate href=http://example.myopenid.com/
+
+<html><head><link rel="openid.server"
+                  href="http://www.myopenid.com/server" />
+            <link rel="openid.delegate" href="http://example.myopenid.com/" />
+</head></html>
+
+
+Name: from brian's livejournal page
+Link: rel=stylesheet href=http://www.livejournal.com/~serotta/res/319998/stylesheet?1130478711 type=text/css
+Link: rel=openid.server href=http://www.livejournal.com/openid/server.bml
+
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+          "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+  <head>
+    <link rel="stylesheet"
+          href="http://www.livejournal.com/~serotta/res/319998/stylesheet?1130478711"
+          type="text/css" />
+    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+    <meta name="foaf:maker"
+          content="foaf:mbox_sha1sum '12f8abdacb5b1a806711e23249da592c0d316260'" />
+    <meta name="robots" content="noindex, nofollow, noarchive" />
+    <meta name="googlebot" content="nosnippet" />
+    <link rel="openid.server"
+          href="http://www.livejournal.com/openid/server.bml" />
+    <title>Brian</title>
+  </head>
+
+
+Name: non-ascii (Latin-1 or UTF8)
+Link: x=®
+
+<html><head><link x="®">
+
+
diff --git a/vendor/gems/ruby-openid-2.1.4/test/data/n2b64 b/vendor/gems/ruby-openid-2.1.4/test/data/n2b64
new file mode 100644 (file)
index 0000000..b12a246
--- /dev/null
@@ -0,0 +1,650 @@
+AA== 0
+AQ== 1
+Ag== 2
+Aw== 3
+BA== 4
+BQ== 5
+Bg== 6
+Bw== 7
+CA== 8
+CQ== 9
+Cg== 10
+Cw== 11
+DA== 12
+DQ== 13
+Dg== 14
+Dw== 15
+EA== 16
+EQ== 17
+Eg== 18
+Ew== 19
+FA== 20
+FQ== 21
+Fg== 22
+Fw== 23
+GA== 24
+GQ== 25
+Gg== 26
+Gw== 27
+HA== 28
+HQ== 29
+Hg== 30
+Hw== 31
+IA== 32
+IQ== 33
+Ig== 34
+Iw== 35
+JA== 36
+JQ== 37
+Jg== 38
+Jw== 39
+KA== 40
+KQ== 41
+Kg== 42
+Kw== 43
+LA== 44
+LQ== 45
+Lg== 46
+Lw== 47
+MA== 48
+MQ== 49
+Mg== 50
+Mw== 51
+NA== 52
+NQ== 53
+Ng== 54
+Nw== 55
+OA== 56
+OQ== 57
+Og== 58
+Ow== 59
+PA== 60
+PQ== 61
+Pg== 62
+Pw== 63
+QA== 64
+QQ== 65
+Qg== 66
+Qw== 67
+RA== 68
+RQ== 69
+Rg== 70
+Rw== 71
+SA== 72
+SQ== 73
+Sg== 74
+Sw== 75
+TA== 76
+TQ== 77
+Tg== 78
+Tw== 79
+UA== 80
+UQ== 81
+Ug== 82
+Uw== 83
+VA== 84
+VQ== 85
+Vg== 86
+Vw== 87
+WA== 88
+WQ== 89
+Wg== 90
+Ww== 91
+XA== 92
+XQ== 93
+Xg== 94
+Xw== 95
+YA== 96
+YQ== 97
+Yg== 98
+Yw== 99
+ZA== 100
+ZQ== 101
+Zg== 102
+Zw== 103
+aA== 104
+aQ== 105
+ag== 106
+aw== 107
+bA== 108
+bQ== 109
+bg== 110
+bw== 111
+cA== 112
+cQ== 113
+cg== 114
+cw== 115
+dA== 116
+dQ== 117
+dg== 118
+dw== 119
+eA== 120
+eQ== 121
+eg== 122
+ew== 123
+fA== 124
+fQ== 125
+fg== 126
+fw== 127
+AIA= 128
+AIE= 129
+AII= 130
+AIM= 131
+AIQ= 132
+AIU= 133
+AIY= 134
+AIc= 135
+AIg= 136
+AIk= 137
+AIo= 138
+AIs= 139
+AIw= 140
+AI0= 141
+AI4= 142
+AI8= 143
+AJA= 144
+AJE= 145
+AJI= 146
+AJM= 147
+AJQ= 148
+AJU= 149
+AJY= 150
+AJc= 151
+AJg= 152
+AJk= 153
+AJo= 154
+AJs= 155
+AJw= 156
+AJ0= 157
+AJ4= 158
+AJ8= 159
+AKA= 160
+AKE= 161
+AKI= 162
+AKM= 163
+AKQ= 164
+AKU= 165
+AKY= 166
+AKc= 167
+AKg= 168
+AKk= 169
+AKo= 170
+AKs= 171
+AKw= 172
+AK0= 173
+AK4= 174
+AK8= 175
+ALA= 176
+ALE= 177
+ALI= 178
+ALM= 179
+ALQ= 180
+ALU= 181
+ALY= 182
+ALc= 183
+ALg= 184
+ALk= 185
+ALo= 186
+ALs= 187
+ALw= 188
+AL0= 189
+AL4= 190
+AL8= 191
+AMA= 192
+AME= 193
+AMI= 194
+AMM= 195
+AMQ= 196
+AMU= 197
+AMY= 198
+AMc= 199
+AMg= 200
+AMk= 201
+AMo= 202
+AMs= 203
+AMw= 204
+AM0= 205
+AM4= 206
+AM8= 207
+ANA= 208
+ANE= 209
+ANI= 210
+ANM= 211
+ANQ= 212
+ANU= 213
+ANY= 214
+ANc= 215
+ANg= 216
+ANk= 217
+ANo= 218
+ANs= 219
+ANw= 220
+AN0= 221
+AN4= 222
+AN8= 223
+AOA= 224
+AOE= 225
+AOI= 226
+AOM= 227
+AOQ= 228
+AOU= 229
+AOY= 230
+AOc= 231
+AOg= 232
+AOk= 233
+AOo= 234
+AOs= 235
+AOw= 236
+AO0= 237
+AO4= 238
+AO8= 239
+APA= 240
+APE= 241
+API= 242
+APM= 243
+APQ= 244
+APU= 245
+APY= 246
+APc= 247
+APg= 248
+APk= 249
+APo= 250
+APs= 251
+APw= 252
+AP0= 253
+AP4= 254
+AP8= 255
+AQA= 256
+AQE= 257
+AQI= 258
+AQM= 259
+AQQ= 260
+AQU= 261
+AQY= 262
+AQc= 263
+AQg= 264
+AQk= 265
+AQo= 266
+AQs= 267
+AQw= 268
+AQ0= 269
+AQ4= 270
+AQ8= 271
+ARA= 272
+ARE= 273
+ARI= 274
+ARM= 275
+ARQ= 276
+ARU= 277
+ARY= 278
+ARc= 279
+ARg= 280
+ARk= 281
+ARo= 282
+ARs= 283
+ARw= 284
+AR0= 285
+AR4= 286
+AR8= 287
+ASA= 288
+ASE= 289
+ASI= 290
+ASM= 291
+ASQ= 292
+ASU= 293
+ASY= 294
+ASc= 295
+ASg= 296
+ASk= 297
+ASo= 298
+ASs= 299
+ASw= 300
+AS0= 301
+AS4= 302
+AS8= 303
+ATA= 304
+ATE= 305
+ATI= 306
+ATM= 307
+ATQ= 308
+ATU= 309
+ATY= 310
+ATc= 311
+ATg= 312
+ATk= 313
+ATo= 314
+ATs= 315
+ATw= 316
+AT0= 317
+AT4= 318
+AT8= 319
+AUA= 320
+AUE= 321
+AUI= 322
+AUM= 323
+AUQ= 324
+AUU= 325
+AUY= 326
+AUc= 327
+AUg= 328
+AUk= 329
+AUo= 330
+AUs= 331
+AUw= 332
+AU0= 333
+AU4= 334
+AU8= 335
+AVA= 336
+AVE= 337
+AVI= 338
+AVM= 339
+AVQ= 340
+AVU= 341
+AVY= 342
+AVc= 343
+AVg= 344
+AVk= 345
+AVo= 346
+AVs= 347
+AVw= 348
+AV0= 349
+AV4= 350
+AV8= 351
+AWA= 352
+AWE= 353
+AWI= 354
+AWM= 355
+AWQ= 356
+AWU= 357
+AWY= 358
+AWc= 359
+AWg= 360
+AWk= 361
+AWo= 362
+AWs= 363
+AWw= 364
+AW0= 365
+AW4= 366
+AW8= 367
+AXA= 368
+AXE= 369
+AXI= 370
+AXM= 371
+AXQ= 372
+AXU= 373
+AXY= 374
+AXc= 375
+AXg= 376
+AXk= 377
+AXo= 378
+AXs= 379
+AXw= 380
+AX0= 381
+AX4= 382
+AX8= 383
+AYA= 384
+AYE= 385
+AYI= 386
+AYM= 387
+AYQ= 388
+AYU= 389
+AYY= 390
+AYc= 391
+AYg= 392
+AYk= 393
+AYo= 394
+AYs= 395
+AYw= 396
+AY0= 397
+AY4= 398
+AY8= 399
+AZA= 400
+AZE= 401
+AZI= 402
+AZM= 403
+AZQ= 404
+AZU= 405
+AZY= 406
+AZc= 407
+AZg= 408
+AZk= 409
+AZo= 410
+AZs= 411
+AZw= 412
+AZ0= 413
+AZ4= 414
+AZ8= 415
+AaA= 416
+AaE= 417
+AaI= 418
+AaM= 419
+AaQ= 420
+AaU= 421
+AaY= 422
+Aac= 423
+Aag= 424
+Aak= 425
+Aao= 426
+Aas= 427
+Aaw= 428
+Aa0= 429
+Aa4= 430
+Aa8= 431
+AbA= 432
+AbE= 433
+AbI= 434
+AbM= 435
+AbQ= 436
+AbU= 437
+AbY= 438
+Abc= 439
+Abg= 440
+Abk= 441
+Abo= 442
+Abs= 443
+Abw= 444
+Ab0= 445
+Ab4= 446
+Ab8= 447
+AcA= 448
+AcE= 449
+AcI= 450
+AcM= 451
+AcQ= 452
+AcU= 453
+AcY= 454
+Acc= 455
+Acg= 456
+Ack= 457
+Aco= 458
+Acs= 459
+Acw= 460
+Ac0= 461
+Ac4= 462
+Ac8= 463
+AdA= 464
+AdE= 465
+AdI= 466
+AdM= 467
+AdQ= 468
+AdU= 469
+AdY= 470
+Adc= 471
+Adg= 472
+Adk= 473
+Ado= 474
+Ads= 475
+Adw= 476
+Ad0= 477
+Ad4= 478
+Ad8= 479
+AeA= 480
+AeE= 481
+AeI= 482
+AeM= 483
+AeQ= 484
+AeU= 485
+AeY= 486
+Aec= 487
+Aeg= 488
+Aek= 489
+Aeo= 490
+Aes= 491
+Aew= 492
+Ae0= 493
+Ae4= 494
+Ae8= 495
+AfA= 496
+AfE= 497
+AfI= 498
+AfM= 499
+AfQ= 500
+AfU= 501
+AfY= 502
+Afc= 503
+Afg= 504
+Afk= 505
+Afo= 506
+Afs= 507
+Afw= 508
+Af0= 509
+Af4= 510
+Af8= 511
+AgA= 512
+ALDs7paJl5xPh6ORH61iDA6pONpV0rTjGiTkLEW2JsVsRKaRiS4AGn2PTR1UZXP0vXAmRXwdSegQgWPUp3Hm3RofRcDh1SykZBLif7ulau1hVO+rhwRyKc7F8F+7LcMf/v+s73eOXUDbbI2r52wfr7skZy/IELhsC8EK6HzhACI3 124241322153253947064453752054205174382289463089695815605736438952932114700118408072544073767229325045596832952652232288773280299665950768731398747700657715829631597019676014848183966683866396215048196276450953653433516126074463193382764063985175903718735372053536664711482497859539116009770850968340298474039
+AOzgU1s6Pd2IkrJlvGND8legXTe50nyDCocI5mwT9rW0YsisY5jaaEOcu51BAq9MmXBPeVX0k/jlXwH4Pn3mCpUAU1rEOsTdcmSJp35siKliDdhTZHHdZNMW+igfXGX5OCsA/BaBcGnE6NnrGWXKyTOoVUGQLEkL2T5yhNUaCT83 166340174936369324883416612727439279977041963320514134445183426741643586944819834936989524033374309932122967866930503619179389342537723598234062828695747850043368572301869699886931403612266216965783079972698791813140295203826980649434652168563255385527187360027803388963151668338040517316899628026707657178935
+AO8hrpw+lDiJ13JahLtCb1RenupQcNd0wlTSck9OLL8wB/x6gAoj0PTLV05eZIbz43N3GUSDmmckjlxdHXiBJ9rsoB0P95l1CWIV+4rXblCqxmOdmlm6VZ13bqbI0x7l0cjeMrkmk+yJ067WqUolqQBlUWMTuJVfkxALJYH5xr/C 167923899524385316022824282304301434707626789716026029252173742527362300338760906999615029022863637963070711762128687835779073122264515776657475985362344360699359591353388569856862973447791264902182048648600267737826849280828116753682917256540180401899752566540869918949003470368970029744573140084219550547906
+QxAn7yrdVs5tlHV+Glbqdaj67c6Ni8am3bBLOL8PV5HbdrLf2xIPmNugo6MfUwFSnT+ZPJ51+VTOsItaNwCFju0Eh1cqyP3JWyLRPE7emKuo6xRhf+5ik0pTg77LEF4JXW6ofDqirpR4alFi0G2d9yImQPphsYJwYGF/nNT8u0Q= 47093316905427544098193936500644355852669366083115552072584429220248776817916430034648347490325490701471113667554329499736495877969341478442613611948220957798780043076906836236556612316544460763366275536846463456405604189392790111985912854476264292503164100482712281088955640964034295834935468665872932715332
+AI9PVzrbJUvmCihwSFans1lBKwudGEZpWWu8pkSK2zVgzGhMvUoGgMp6TG2zsUd1tV8zv7KsVD2t6pXmjT1wPUynufq97GVHI06SGpflDTt30WboYRh3DgYxvso1sOjUXpnDezcaqc2Aiz4nV5MSShkBlyBjA8z2worHDE+uXqw0 100635651531872121827765663065728398779771663753008344681972226973080394359405041113312675686974926993279775427390065833081040771269307007695807025882757371805607979134114890454059957194316765342461291139168706134406917264848659448693866813989352429841300235734400772946895458374870482441457514575059390213172
+FiinVicXOqqRLpxcGTorQpSAGeQ/PfDOuzYK9ViFtmPv6D0cYPfhUH4qXEHOejvmX+0b4lfaX8MWPVZxlqpfXiU9BhG76HJxkLF4ysipukeOvhoHzvcxE5bnhSF1i//bOSifATBLBEZInwqSVg5tHHPuuCkwTL91NqhOulp7Lsk= 15560440884463435471963622630292643727112462888414585143143739400703889930416938984547754943252935620248108237258540176511252143752416771350868493435174026287082706690332705481726295797196444796135827460509780634261726494455068460028424141500629527968240913757449787164107068039175831847071025316475940056777
+aYrxyQN/hkBne2ayqo2/iDLF3DZGgk080SOMJfsj9h3Z1OfFZM7TJA+y+/O7niqatosvKrfHrAw+Qs7c6tCZ6NPwYJ4QJLOF9bqH2u2a3fkI954voNUctlUagYUJsZXV8hdhLM6NwUyIZ3ZFkPcpTZp7nKQQ84tr1a8VjDIT5/o= 74114640794666001532816944350975062126079079113921109750255283189037502412929005615388097912507598112836936032143435813588205939470002911374442844578739574773399427907766548612582213272643279263782396527705126350063372192910060171635870872236876399794128383338399728947176692692942605589343038282957050865658
+AMpCUeKUX/vtRslWiUUuXNl1KA9uDAWjMUkTrdsxxRDESI7iZIn3TR9lW+0kV5fzkLF18iYLAwSGBmX1PS/T0UVFmoBPJ9yS7yktNL0lpQ3noyGFn8HHZ6XB3FkH3jegIfGbvwwhnhhFzpHPrXlpO5iU5Y+rexzp2XHWt4yJGuIL 142031143422642739313498629438991149460874309300342349421794421544918823888598660275343727563280565210534243383322796489809683834300630555650331646026843796764549231159336347965502383849513994449309613369541991287590422095953275586374371960367000083487965487661436037637475372929033613295072397262739084075531
+AIMIQVz0JIEKEI+PREu94m3v9XoiU/Q0CpsSuqkwSSje+Wyul5ea9oU5qgtOpdkMUOW91BJo0DW/GMZ8v3C4qyyP29TtjCcAHObJi9hfLSlnTSuzXZnDStooYYKqzfToLToCaAJKCXiXAVW0vWtapLnyqafrf/KgyGZ5u4HfXKY0 92013973253053602863003242446596060337454881568126916916519869242232429836082762281129448384605359749247852792606718908482332975424967542242332487707042773885428473061056052851768940900752317020681189773407893371297668591494665352294885305475871917069040377145530889271334616499701769138948975263435137525300
+ANfP+zPBTR27afneyac1KJhOB5Pq3AXB+SoAXJvQI/GkSoNhw5KdfqoIkLcoJi8wClCm424Gm1AdrdGwDFOM/iKTSPkrvMag93+b2EbQGX66/n2X3YRFNkgq/Gtb+2M8oCcAL054Z/iiMD67aU5RWzjqS64ePHsn01bJ7dqLgpMO 151548639867177154896951257541227014781655576169318283047778755573323724856619156348444192550664853912434681577093459933599575436686424046466113215132845213008587152894642577278656978304699131916299275797578171518984206145555369576872231567191579337901913492071976578289189524123204040497290426960375042970382
+AK0kHtacLGu1NFWMADq2rG8hpzM4UEYyPOL+aMJbnwXcUYptRIxb0YFZg35RN/RiZs4lQsiq+kEJKzMMV71TsJq59vMkIZhZoB3t8g9ZqBZuq0JYcTICDwRpNSttJidVpJ6P9sR3s1xPMYKdlSwt6EEc9htOXfZU+yHKYgn98X60 121583812047864398969816595368193171848971298823388059338224714026742264861090347096116404814514279627148994345584790617974476594451626305761040465570524035369799925437276511604752129817947910677564301623631349399504187314174538914591944778074509068973226322566160587813128746039859381466427380402262866230964
+W3sZlWW1Aev3x/DiH9MzwCAZzBj++x9cknLfGAHwgFqkLH6vimEH/r8hi85hzlCOG5CjwhoZ0D/Hsfr26ZJ3X4chG84byrfDnek1V9mm1++v+clJvlYgcuVgn2Opsba2TILTm1MDB+Ujs9brJ2AAKrE9+ep5nvtQVeG9PUGtdlo= 64240043913835461386212515483198059541440539167395859777194837833769712010594411295323900074066077107346806786205590345517755715510695858065925747020336398305793661773798243627926904542715123849691490667964262778804487343218972081260210371192903128886030021862362141928329650003493687310970684093289133340250
+FTQRk9/BIj21gbLwI22fHJWYj+8Ghdcc613hOtJ+/hQmh73HwTXLpaGK9aCptxVbpjW0r/bxaRjmgxu9u1CCZh5yRd7Z46Wk/LIPXGd3ycQzqRMFB7TISFQGJIcFoxRp3Eb5wa2OyrUg7c/D+kb7oFJq9P7mEwIh8TpLzwmu4SU= 14889529068556301710329043521845510156960298822469914567758538023025100741826628180855835334285179977296740667353391766487166458692144569279381035582718738461626140662441222061900764829681913534146898551570916312642104487829660946024590782808750587095559047648957238487820069966851521487428624726655438348581
+APYXO6uGvs9qWiEAkcWsaCaCrGJJCP2Z1g++XlJ67oZIgEoWITn3T/R2/c4edAfwUUzNHAYZN1h2dSrRoqlrRXrbxFtGOuRCUrXcGLFFcEbTrtm+z5z8xGRfcorx7Cu3FIBPMK5XcGPcbRZdyP1gBkeDMvuBNAo0/To+LP/dhCNM 172810804474418448604443090732221483571611665465870520701624598983692130272337358406272727413570938793741430131635927237320173996217984860203754686741782921346604605228620148450611724714868551781003004492587584071978757421616871762681049508123223983431502852926521520561941051298696758046005573332373854233420
+AIDNxhnDEe1kTJ3XGfTS8zKXeXPRdw5yifm8j8Ibzj/quExy7hFPtKct8hRskPR2qwTlMiW9Ra8Npg2USsqHV0rBoIkX7E3psxq5LBfp/q00l3SEBuLL4K2FGR87bPgU+Duk3RVrNMnColiTcnAR5XkoeWhn/r9MfJMIN9Y0FEh8 90449107125498302548188660544012777357148118984122992664008792590422284061463729084479315745509706793674355738023180454297730948397413371686013210006834869294564190666543874617716180411178090109573192518129248278410216362657350215009192850017507998797754539132540293137589672869131300859207213449571846080636
+AIRWavxYRsGlH0Yr0DudwrpYtbrByf9ZsDawKom7ubiRfepqYzcBlwt4adMMnkYSaXeYtOsD4KBm2ZvLKN3++RkYNmxgkyarORBEg7ERyiThBj7Ksw57pNHCAoHtBEhH7Wp9mHhuZtPvPgCEptmwCu9rYhLt4zZp+Zq8a02dkXvM 92930601962515884925250459851491509622611227724602941760145671636277317511265759558869239180653492283311584982044597979173761619470326096725838197524704577188104121460089235709339932110663536557497112887112782062772810759971739760085128369628777812332518137107605855679096146402427144185104230596200130247628
+AMNJGLcAiJtL5fUfkesWKYJurdYSnvsOZeZcrg7bemkEVjF6S9CcojimUl+ncr/YY5/EXnU0mg84fObtDxWWdJ7z7l0CFcoALTyEatDYKshT0xvdKY3u+LUShxIAyk8EcGnf+KoEaa4Mx3tg2oTBnVegXClOakNTWw8bu2ItagoQ 137134165107366719462230252606689766470445826753581409513106273517221906418464863733870948759313279128624638614534848890858250894834883265387344539280755177217350585564186248554307335197387734431939154077778003706720017441895613190141376534460438929588407764609772857975000507660651583780079804513519571438096
+BmGPZt8XqqI1PuLN4K1/PZMi2rfOYtHEMrcwZdSjKRm5qTkd0Pbb/5zPV07TnM0uLRvIQYTLloEY+GYyn0K5gDTEZpEtQ8ee6Y87zYGDwcf20eqYNxkA7FVV71vqCP/Uw3Oi6B+hMvsWZbvv2vH6MkAeADCrezOtwqVS+irftyc= 4480956865245875120472829476982311611308898564405318773810939350829150182630548948231116574193987272498161864310429976564278532538229396846813874244969927890037756969704618336242255039858182439641759659872128285423988638335967412040624105824571426792562334458751137508116412821914961236269913776304372561703
+APqFgCIYbJWuRyEGuOStPvcprj2PILQ0JpgwQ2jLKn3DvkWSd83qh7PWGKozGavsjh803K+ZzI7P2wP+Nc0r0El3q4nzaHvKaCtVRyMwbXv9wYLFZICeM6J1l9ljUMts4tbDoPzkIy3ScU7pYxarBWqMkcBU8qL6NN1vEdkeu0fW 175922170410080716883576123079908758276229469783745771772401183721225804343343063277676406040455068452258961299511343441961963941297631097736305638850193978800615558067791016294285848963023036905095022181004058235239390870177623185946205281141386416867569004073524130001309977475780893497185890756991672600534
+APA/rCcGeH6A+6ZwaBBDM6mB6tTD8mjkrOWEo/pK3MCZ+rrErMBnFp2S19GhlLOfuY8BHS+D834Fdm8+3wKYkWnXZpGb+e3v8ofOQ34G1HvzULOYtrEiC4ISZRt2SSyz2hU+PBXjVnplsHWTRxZDmBxTJdgli4ItAqxGCxj/aJ9m 168708388929747822981923386197903561880341990893945097067702518857172133291360611402092714329372304718329568897960770488377524912057166920574319430820488930520807742026377043178502591886293565177404635365772829346030773275726024973460121300339258054215286249329967181244588558220467488638468686270735376228198
+AKmwrLP108dCGWOWxE/6woJVLRi/Kra/DvdsPkkrZQmWIlUT7IvwM4gU6bUr4f6wpT08cIQls2cGh7dbSEaO0xLa3mmtKhPiAlzSnz0wuifF3JT9U3uXgUfCZuFtE0z7Oi7WTOrpl3k3GA7JFvXnY0lwblIQALVf6oWyNETnajGl 119160465301384937485959146028591622947513292915838943629387700439301197965652871741710280647524383590817798553034250156068573474278225305190573334054718387045488098320076877626430189054572361967283632592181431701411266656256255758079114072932140551282607247364388070762970060420036793573956057551235306893733
+VTe3rCzAL1Sljo3QAXEkAdBy1ZARHZwtrj6ZNRa5ttqd6/l21g4z3iHCeGo4rnE2F8wYTy+NlugjXw86OS+XojW5y6UzTtx0HX5IJ4POqN64aXWmaklGzroBEYWeuFFKcgQN3NOxkuJoDQ6VElP7Epz69kj5CsKJUwL0SjbNrFY= 59841866347633473702601462509811342285929528424012250265905695635971518533504187799047710303717472950129869674786231155102509311322791323986824635569605105662070745033595366004805920086888891275288347907772640070278731650628917037915863439204501060041944275512863990729926528905725569467329169134226609384534
+AIZt1xGhC/HrvpPASsvVIVdsu//tn0noyJmVYh3FdQ6yIh1uce47iCsrV1yvYqx5ZTbC0vnfnbjFcWqH+HtLX/DelgvhEwzqJ8hwQrfE1ShLG4ZjAVo1Z4GCjrDcEUMlwKcunuSJssuxeQuXwTLS92+q6QeBSS7OmfxPX29CLb4B 94399298271083745508290936113986978382457275531684761701599029877008571741877683365769553170771833981099580359640421358853566501815723434822307977440496208486103754978934472597505865596938563438311337045817621762649604204720249750058676095769230214181772215323235427976398686727606000594646472236822594174465
+NIhTPpWXS82VTA0LTd6TfM+HgLgUcmvnMYtLqPpuqCKZwalAycwl0XFYNyVvaY21J94j92ts/lRYgVtHDhk7/9nLXq5js/lsUnG8rWPHJo11JTxvW+df88aX0pw8u+biOWt87vc1MW1dsMTTsJFJAeBx77qU/Cwto95IVqM7vSE= 36889590210230649939994518345793530042252563793069578097360569338647730438860274349862767107939590441616825589851005429465345268710487649366046960918184701290985280638488938340668212498212581853679035928093386035688597446809895381618260692378376844452061580510108168030682664507293277674052032318576713776417
+KXdi4A2Z7tSiiX9YGtDtxUXIfQvPhcc48rUH+Q2SnXL7fLNmr+F4Rf3RiFBRiHKocPfE94pothop5qQJ5X221/DbEKWK6s+ChfQ636jvRxojoLMab3dPtaAPpDJHrfZMxbT4ZaDJT0tpA2e+zZrzBuDs+UUgCpty9nxtdm1gS7A= 29118662951481660380477444121362422614202367719725087486810943918529894738076273660245405874301505615796632229852040910511025841576465052938308369421493312085081188509808322692130449282585522349552501983296872614029139293444558468751646868108213623606366977549477663987815308260383403466635254115908032940976
+AIOTBZQR2EJJRmoWdRNFLG4fceoS3KnRTHRpPdllhHODqdg+QxTOcOvqIzBqgdD0JgO12SuNAjLQOiz0jhd02qkXw9Y1adGuKvL97ARFtNEuJiNzFAj7KpDLy2zk2rPJp4Lp7cjQs0fe8BQYnTzTsNRGm+4ybln/gse1YWu9w8y5 92394618277596007469808288231093678404089765494062813665106014405059399079199990128824492247005602685377185496959522609467906358619318009231448503013528692450191782140091818984176967246749464502089280153086163239846744554575017530385347720563798041108608545014076448155956762636929707905789978331102411214009
+NzfbJRBF4pqEeborJrjoknJgpfK+DZh2k9cE5dcElMPZ2Zn9im7desWGiBSQnu3KbTO4L/t4+m6nFTNcbIJnqbVSMDHdsfG72rG/t89aOuECQw0HMVVgONNNa6i/mw0jZSWnRLD4fa1YgbUlMd8jeqO9XcBDB4mVtDTxyeGa3vU= 38775530011374537813502898274019389132620116890266344603221997943675706375698597061571989090674289834838060050848545748579361837989319487970580969082824601965845786771062335733318139530316825802589479118956745739691326447349403950997231306042638797277408335778415717988679050762936401945487285814799382535925
+Y4BVPZ6necuLSwaqYEPeZp0lt9tTGFl/WCJJbwg7XpyvuwYKtzagC1NLzY5ymBfwGFw1yRlQuyGsYd9mBfC99DuVCIeh0JNrhJN1bNfoSzy5UO5+dmTr+dm66VGSRS0tFcViDTfCIleTV+zxo/xuZT+Bjxq7kZue8zGkjp42Kmo= 69872189501616471647606976308259279995249122669120675885925763529037695584466011511740991152346215507625265226811128801733353566555339153627478941716586678793853828514394269931890370517258825006937741437480128878717892485074131232336852490940507703859793477547154689914725314529986438108117871674332626168426
+AKCP9Mto4q/a2xNqM4N7PekbKspwt48OGPre+iqVwPrSP/jWKxg3CvvLNZzN5P+/FiUGIklMMFJ8w76OaHIPqKuwckj1gvCLECJEE+UAZWrNKPmpzd/ootN9/kQhNMuloTFCyhXAUUOXJ7Z0WVLb2u6fn4zroszSMBoWQEKC6lcq 112750701794692134675959811050012620191158543234019977304167102486465198271340022889272244811582365901584420008564301920174477182946432553537794834985703732129975734658113610563794129371053853971031300761815004524681756388784922001759202643614966614186697992611399618828963452661554240362943588548146868410154
+APOTAFA2waoAODECaGNgCHa8dNN+cjMnD01M+IeQFytzo9RLMzzzg/gpTUFpyLtFMcfbCkDYQMLXwE4crTimdz5sVvjGQ+5fSFQjoDY6Bw7MO6NAcLzlV/sI/1WyNBKaLQbcl2720n16tdUcdckQNnV+cC2J48CVxYM1c7QQlxA0 171043636512232272455501595416608280460445723238023572475354665686544174728784633443479486247342724860289312593374524429736857970220153680852977711594899595712511352458264354251161579203922747468321999465061463474727943140910084880926005209538535217464825087114791420210981711903880998556269523363208766099508
+AMGpxRlB8WVnsGqyyiy3/mzrPymtJW1o1HcDErK11ZwQV5PwTF3c0THwlnxDmcziLWHSWgPQwfRddVDCXMGW9BffJn+XO6aTcWDPmDAh+1DbWJPE1aqApGbHvQ8HONy90dQMZf1ayuwceWCVTuU1wnHdo9F/sIsRbuu7ic2OJDzY 135994898408425255747055209966103741651849229328236418804928584233229830656742052333413774490626915784901255640138520158698845938184666683995579777154437927013722740366497459963753542029774185193376253885864514386760437194444013834088425088260658670140534670789371556026135595577395047002643901630053097946328
+AJAw4uDYdSYkOrjtwJVWLv3pi1+UxWge4RmkWKqVquTsAVcT2tRZ+MFdHM457Hl7fmFIyxvGZQy4c2v1AbHEfPR8ID2sCRQpdcfrxEUZPMDqxfnHHm0ziny6W4X6ggdBzMp/sBWaVNTBL0e61/pELBGYNRGFMzGws7HQkr/sro1D 101254336834199527040756567675327011562230719161388328289463594628690618298993695452746353237675715087353241661592074446889034411683413957950360025295995263477031608845241728493807755308798509893719674568267846671753070163272328014412744008880395248474446310603301447848026040555910147467745595720879397834051
+AM09TdtXgYL4FI5CGNiVjf0T/AN/pZ5zZsBOi1MAUKMURiXnc1x8VKYTqM9Xb86mqNBBqphynIQG6/3e/YbGJgHlsSdrmKbo+P9daMr02I/7Z76/7Osa8+7Ky6lhVCbU3F0tBH4WvopkCQmuJ267afgvDD5kB+9uNr28deMH00cY 144124056591600568767398029380314564902309327093641173350205276895603332085753288682409279238417493662029954512382520307259348748813767324609446500382301421328754981718014234615523158887865271179104711373675849713359713282937065993613915015084108700238420759344034475478243507306107546245540340758766909867800
+AKDhK+/BKGXbrbBh2vM61OP8LN81YwlJKe68KNwUu4tjXlQg7i49Jis7QKPI/YFPUpSNTu5N2iCgeMnCX4+r3NAfivOao9lw4N3nc9bi839SIWdlokhwBHBYmCIgjehUeBAdkU4jKqlE06pIrpRmSvBtn7O4aWTbT+C++ViYAcGF 112973480670453665543892521898882856059335781900313607790238402438320486344365203510769919022496690291280873287383392088872774202832124927485754495093552572232234532821756395965072330282810574669371524103814871172318519695921477775100282448247625395376072233777533359104085023946019406729587713120941266551173
+ALxDiSxHjfxvP8ETvpE+SyDPTS7q3o3zCK519WTepygC58KSRfvDnIVIyV3toQKzgqD50kF1Ni5D/wuaSs62y3zg3kELX1g+WuBCc8+x50+kDtbHXa1Me3et/OqVS/QeppkcjK1UZMU29fXze6P/w6aQfvKQkE7koeQtZBKkYc0p 132203344567902304830160099595561253300484092355345272411265169562971473393256361094745618829297250316196312398486598077249124198329075791740755862221465178128527292695331061023291345396067863215552021206609309872689233899464919108147533679134727064586730810633196817136739658243232643507412032417747255282985
+VF0YUTvy8Mfi5o6X06DEvLm87r72mAtTdyyLNr0/GXlk0Xj3L2Oi2bVUMtcXQNRXg/mkdGP88pgdaP/eMzqkUU++vJ7t3UgOC1i3SHegpiBhhZh+aZHH/wjFV8Mz2XZB5z8MpMgN+QwALK1TT2Pyt/feQTsOy0imVanB5+OvCeQ= 59242171319056188000481457618922567543461456096441095927600135114274111606802456239311634638536207588762066940095527920532936960549439269891703098017342732142860571277442598349453761561189719823290643146391349978698217357430495238876700400634593256155537598291759795109752990651995982467695091946768443574756
+ezpwBt0N6QhTusiPcKrBvSB6yuk/KShTLUFQHdf5J1u1fgDYrp+aOWuXOFVfOd0bweiG4UxBQNXB2IDFWfYON0fBoaDqNk/41YyqXBSkKbiNWLi1y3zPmwTAiwK0PzYp2EPfk/t/j0HsDbvebu0ygcxb2tPqj4EQ1TXEdD007kU= 86533835313999945727720083706940213467453975054116752898416709637030456504024135513972566184073843025739226187558143854850980654667596935003124034699919861200483994576288766702308068265526535622439762454501169018136389983894783905946543636163866717367545972667876983557989192393479830223914708619684891389509
+U8BT26zT46tTZnkmTNxGUAlXbJhk5cNi4AMSd8fSvZHm55siMFGJ8Jl7mtdzEFR1UFAyEztf2fUhxdtMLe8ei/OJgM0j7myQ9STucEwnsShT7QS/DjBmfvcC42sl1CRpXkb0ZLrEJCPf+crtLKGrG7ExS1oawIAgALBiMQIL6mE= 58812148564290791415180898639607206220554150794356494356250223429674091688305329629529905854147200457536549527135776329004085047145097927266797668252160196098870200925284256433894773392353678965699083286106628662506590268955650280670838340651598082083455821825076016227525614626726458235627297885815646710369
+HfYii3U1SIkBZl09RHaGGA7H3np+qxyNeeCNY07PDl8LwZAaaYk/bHPeBVboan0I2X4o78zCD/gFXFBJ4rxwwUsVjHEioyO2JcpV2/oDOelJBD//78WzBMMSWt7ZKbJV9uYr9ZUM0BUD3fsk1esFCEdnDJdr86U0UMmiig2R+ME= 21039655953870571289679214995029926285040274249531458675115179004718812090027267801012507748013357317597416722235988917212676802092082137617336199787762782958420742299451435320649616271885264333948336627286638368859041172783505464468640994920853000441536629081040963398001710173320125308624362209157720438977
+AICOlee3daFyqTrTdtWjVb5M2rclh9BpIo1CRvKo2bF7NYcjrU0/VvbOnTVXDwdeGMLupbi76f0BrfDxYtkzMXvIZlgoTit4g5ennnklDHFBC5cogaGlri8U28w4/h5oMunZ1O4ezdpRgVJe9nTP/sSEMYiNS5IA7Zshdvm/XccF 90275777798511290102824338787811725003177532250296755103300529948194832904403489332420505850668003332750291879153080212231952155092379375422537931240723308384652734942204313672973885652497290433943089371705605128843469306776615573873479312715317072986990219294942040272550822460408702072075001377245051602693
+L0QUSVIjxvE201b1ztRZyOOxy8vkUz6626TH4tbLwXjjc+AhmrvplaVlavnOgHqve+/L18XNuAYP4BqdxIcWTx+yxBKm4ZS92dRJdcAtccvZpEJtYjdJvI6qbL5Ph6HluaVZwp4dyFyXuZOJGTfYdTb7PUWM0jNT/xsqyjxSQ2U= 33191267986826803728285073844005357792766429917696698533494382218509532051029343127452480789088572904364699220151221680328978554239767633887572649589456766209242252549993823283929686430100804479376247660556781589549613316880150951333982646510273364068770923588389668733632648346075516618646974067295703417701
+APlD9ECKJuACUmQUsbd2GTOpb2PgQVT08C/5hyNEVdA5bWoICX7epmoCKCybdolk+cfEBP6fSz33j+Vn8MbeiHBLdmF6ETbmcyOjldJ902MDvU8dqAa8IgEZN5Uh5x/xzN+3dqk9o0ji7yi291u90rpfIh85PPpDat2B4l5zs9i5 175040148659257809883308984693597046378367187659749953472629929701758633206586720399909808941145946314755491399962797299295431089674294356220216615950668954164397362123668926410543898553191541662075745481299747832013627018846822876386760538344447600390187421938699064459451308870669878673306013635576901916857
+KB7N0tE+A5vFhyrd/m6Qe1wTihkjqmBn+rinfmMAzRlvtxIBSyDLzQsOQs7L4oTG64ABU+YwcWVijvoeZNamaxGl4hatAH1pRqmC/r8FMvC4vqiFTbFHzQhkjM7uoHD1aKnxyBVgjMj0E0KZjrRxydZjIR2p13FXjLP3UQSFtII= 28173452509830313810392326357601136401754938805266458365469366750775669869895498658593356375710132149836430968810246171974040975430205200958564616924399794768861923079158311829444850822144940112488994119845741191519421434257276977333662656888696213514226866147767570046232093727585815615828360199830275208322
+bxFgV7eXwnbQScl4VzS3RTdcMW+NY6pcGkT1UsqHIeDVyBb8DnH/2/Z+DX3zniR1iW6FPdvhJJeQyPIax1ohILa11R27C1TLxGvTrRBGUycxjEcBIxamHveBsXbECWusYLEakeSDg9x4BTWMz1rTQajkorBoeEjYuW+xBxQtXME= 77994515143740690952370766995249847650881300682406161400195705464876513409097078624084133111941171517535435606295232558665316819077765607639545069239931096306624817379462598756505457054433358548941076472902905065316335603665413114267741896000877284610377452471067725794013283338924419969559537339967562669249
+AOH6E2eBzD76QdTJ6QbR/7OeF7AagUif9pEYx7fMqrIsXCJKKpLV/RHIItCDYP2WO4URCaVueoAJe3M/Shj4o6efvH9pf5Q8MLM0rn5MTHWhThivqYQDwjCp1ZsPgq1VFS+gcnmwgHhj2W7XzJxiNPeRXlxI2vL+XTT/wPBYhqEP 158686346608862569574095184731081143351413141116869402750758091813874232272198082464382169470744476593016502816563462778075467588097653320101723165887488327616477297401486647183409348122990505635004320879840358339260797834264972100385692477324858942142372580281421734058008608134075577990829273447077276721423
+ANDDgNXOB/rXwmS4KEjiHj7RCDocVrMv5SU0aw6AJzNTBfseFngqidXx2AJKOEeG7RDDN2gzn4K4qJktF0AIPG2JbELlLUu0MFlpOLxamp586qyp67Cl9OuPq3UZTyQhIsSIE3VQkvxuQkGsaV1owDV3BKIWQbQEqMQI3yT4ELHm 146598844784260148346676185962272439320781765598895126402049215152385925250917998794921584290777625240122575975327405909800121511343265147922400813488099624745229653124857224399973509428158163452130086943873214460600035260925149630502192183407327427517292065083168010281295559088633086659209316582810260124134
+Vprr6oBnWuxIzyTZjuxlKSdZhBc0upeNBHVIlXpQEnN1Q+XURKzp4/6Vg/koITftr3SMSgGpE7LkrERMGFgYaqM5XZ1RXYFKT9dRJnz9VRDITVZtdkDrU04bqo2Ur+jvZhvg/oHBDTgQ4nPLJfHO3+GEmUtck+g/wOVozMMgufY= 60816213163057201559480662231646403262735082707152897397414589876256824040344252799972529759737904461369360580708093117244392116003622336721789703580184437841209963565058475060017600871779929808204093448248984201640754565635410002090180110910120481044515630478472999135146756643143415057403006410330361346550
+do4LGsm0afQLHl9alWF2RVyEKPxLIErsf4pTPgScRE7ZiTSVErbCDeyzd/KHzhBLQs/DhHHcw+OXj541cIRm6jaLVKiT8EwLW/dVG0AkVli83sFh2f56Kk+bCGSKvfGEQcGLY2k7nQ06zoMlYR/xbZCka6Q6kSq4YBDQgigQ1lU= 83252051731120517035090523892596419800592471447735288551342681962005778435125655090199060145942826521644585427683714084736143440310518046334877897672493531918539106001203807757254797471481884534543367685912500572052457610702790097953420236852480969038388056545966568595395722585797418296411673622376893961813
+OL2Qoj4xkqRrQmuuLwrABG3BMMBNGjfBtVBNTdBf7g027Ghkk/z3aK3jKT1EPpdiOdn8zXYBSO1mTRGyK3n7Jo8ICOcnlBOF6cZtDsb9bvSVE26MOD2wzl6irU7vzS+s3vGBkN3AazrxPD4czk3xezA9y13DJVnNzgAgIQHEols= 39844525812817530522650122383059885756573694015271773938493414420875846359054562126060762455794481186614035892021706051863945033061233991184379580556219478200155757966121832613842937722944431875100059046588723473670448006803481527981834627086055642349130254917244469014754132003347635357123155857820000494171
+Ljgn+3Hcg5DOf6usRumk7P+ZrdTBRmo968HdZU1mS7LwLW3Hii2KNkwMV7J77zA0P1pnvhMSEEeh1RbCUjLtSIbt3RIcOEoc+aO0eINF8r99l83xF57CBI3MDA3AAbtaYATy/NUXSC2h4W5kdsQuR88139MFi5y8E5njqxHu3UI= 32456338403763561215581247445990611953939298888251578685087656354454727113846722731945605696397627662593375001096230320486703167389461057538581895745078593206660798580358701927596287363374862536765135996838944212622199018632046955402325290145163082309469649329852148345837780541107029165352782710901375425858
+AMt5/u+ZUNm+Xsucr4RQPUu6ExAOq/Jbcjm/Kb2YIAaEQ1czIL82wsu6YmpHcfMaxLjY+EnaaF+eCWQPeGd1av919+QFbQPeh5DT7ZT9klK7BFyVsN0nEDJQ3AMMJqq6lm4sUeVxDVTmMypYnkzRl7jqzyCRY1MHA+o2LyMECdOg 142886089970163885609957244378225169093559131065687633458877059657380607541767850701139140472705242750285722732461954100519608059127637509286558848391554697942686619832870045594188204522385787253648018847569919409782188708374165437385572046835539379151066214153911415525465041951116179326632238059135825466272
+AMvXeHCaa+zk5VdB27KoS8XpjSUngaw7Gwlq6e2RrkEOxBhU2rGWGJ3fhq1HBNRxDf0quqfYTMd1speisaEr3cIyx9BhYwB6A+Nex/Sf9DSixezhcgEz6c5CfwUYP0QTTOiZDqzz+GcjKikjN7DKJTO0WSXMRG8qX8FBbH0rlc9l 143142496664357119491819741364830737485524654099662921673419335301323845847085335210884201567922636945282124120681371777665458057821603161276185071778040317947168899788341482064834489328957963447735297898161379277478278414388733161844053774747425459239004132791029364174047523473372650441001639174571312926565
+AMxoMXHfE2i4khsAkv/lPtLQhbWUjP3kxYmlJkpacpicBB6z/TmG5zjmTC/sqzBvBn3J4UvMzKYFyk9/l7Wnuc480500a3S4HRVtMtirPueV8v/SPktL67eN2zoj1VZA/Rex0aRGjW2CzEKGwEn3G2bZSgdT8hKv7AypF69ppjz6 143539479941314279463880342636704987025205547180882175105616955926182352311179043850344463145750154442573797875223178075233807385237935671604701513551125937539235111702655902037518920150424691586943553275517626347557879039695678271564616114192941679606063184290901862703975921261779714258077775731727612132602
+ODvOKg7l9RCn5CePG1FfMitkR5l9+7JK67eU+WeA5p1YXCcKS8GbYAKCtXPD2QfxmQcrNYfAc6Yb/kksaq29oW7MzZuTDzK0HXY5xBc/fJzEuvU51gaI0PR3cuU1qRlLqwmIlyt16gto+2E64BgPgIKJcAjx+TfH/EqNeJ77/W4= 39488587053253042573878502921384752550143716864908041972426777545317969264945056510991363961916339225192727727267483337259701961148978214005913510275048195308792987888118270387288989623193626554910652030960235845935461155296845475356011099372367616732243132816329531758943935324760665826550992788664237161838
+AKkznyQtB+PGvbVroM5nUIzhJUjiNj7q4fC9sSFbmDgvehnwPElVlie6PimH2FKonGV4GSaxZ+osil+9omfkb4rO3pq8fy5KcFSw/gs09X/U2eEEcUt/4oSbjs2NaMIxQftM2CauULiwfkWdkMFTBkHnh7Bbyocc8dtmrBDdoI8a 118817437232756222334188081193205110010964766506378146125932730686679941224328135190204402802650523704343176483564284220367074983943319572348376466341132480772885833789613392397284313483009178508647973749522358005819092779831781339778163122774381387989185969990310049504391258988402795259963134610905036263194
+AJfwWA7XnYbTjlJt+9hO/Q/OubHkUkyMYrN6Jd0cN5MG9Rg8W3i8U6oJxT18p4XozkiOgPlF1lE7hIAW9KRKJKGTue+iw0okLq5UNMu2Ha6l5/wzKi0QzRVTBnQm2zjPlQpgUorBBty5mcbt/B/Y3vOE4I3iVXklVtjQ7zIBHaNK 106695084438708194568048926154027115609888551145480521213711726807296356271397749432698558860759334362315257102647885062353922543502466463770991058956633500180245599467233361812610650830611712448187310827443315947425061886163301613989593906515923245020641415290300558869209909418659128196109640872398602216266
+aCXItk5XhuNrbrqJr1Qm04U4y4AzSKDMms11PgVcdf5fCGdizibh6/oZqx5OitM26nRz2vob8F+ZIP0CIyIJU0T1M50dVTbbpwuVNdv/XI6gHekQt0d2g34x1TQJIcsT1VWwGWTPNMtht1hezBAYxwv105AGKnqdLiz04YAdEk0= 73134927546833985031652237686088635686032103401394612286045377544136784429757461671691980910279873140130943470029643791712859175007885735170485461366406852784845528918253441791024065848540598601036357817496637108534035807393364939272891745520961269029038360205258229770737579266643408540634722493263322616397
+APNeoaWlyNa554OtHP8F7GAY5V9F7LMoF2ssg5wBmsgGFktrRH1C4FdyD0COrzIb0Vcko1/HiTnA9JXlfGKc3gTHEnO0gxBSDjK41L+EIgUfR0EhAD9iftTaCoBM7qZN3R1MYrSz3sevQZNMFOOnRrzwWEXnJaPKAZXvsqPzOIF9 170899982929163229592439208307232242235219591108657660041403142612622997092685093132858257827585941687488772925553142105567685213341947938835403410054637382864108739466539574004149772568683507025358331323655651148107044968424043673850583150424463706583215452211942132017052425497789362680979074312857823248765
+ALhwBfBYpOk1pfJcNut0C2fEAd4hhYU03/ZQBqVe/7MgpEDjro7oMvSdba5kjH/VBssmZVqpvuZ5lG+vI9lXLukhwRKJg7m67HG8lZXvjDmjU/PCjxBPNt5r8/DziETYmMa+fhaMTw4hedZcwDe37t1VPIflvM94sBKu6be9yJAn 129516480651398210587505113546142851617282590236388547627336279692965778911450075230961856270046942312918567973875005814982283590898552829322178788678196583244198944578081007477482775130405341039067711963061287597331433268366003672643052056973656674139309732186091974604170508497340243515339072325943686631463
+c9vpoiZvtnj71b8XguD67WayOF57QgOX4V4L++nG2u/RY9VT2+0tJ/C4NIawVa7ScQZAPVLuhV4J50HJX7FZgtY5n+lwMzNo0av7i0IqTS+1BBO8eNJy2wkCbWWBxNybuNnF6OK7eXdPb2Mmwm2OmhN2/j7HAr0cD7rK/Hnif7I= 81358980280155473712258342299472964374474635149963153129588784719499494479288254287754874893180126149146558961101860327826747785201363745989346818037655063262173536227595206355647880155693272153902647256175878517626925488264893732295267833614283963802283320574654949992393798458265266551024756663538388467634
+APArEXNLzDydcHrieLDReJryWxFzcsN1dxjpJIVGeJp6itsJOrUtnmXVnETtaZhWsmN3/Zh0R7TgJ253f7PZ/Z2xCEdqF0hs2MmnERSywdWZQ0a0McbDUUaDjBNYFht1wvS6djbI1b8RfayrnEZ0miYdzrrP1ntU+5cM1QBAvj6T 168651870043094856205824264282870999215855903395882323164614939540734011037112413507417141209480771157672307388419164831992909066097194364645695794831939514470650008210390333649278806163193463937050083854756730458780288720541495880958909249273048328511615821480782977316719631334570687241232556472064072892051
+RhGyx6xibf0OvY1XjnmX5na3G7emG8PWbvEa1kIjR6pK6K1MrMZnxFefXpHWInFS7ADESNI9LHjZB8VW5QrjRVPMksgdEAlkhY7MyQxaclUlShFl2AfKYBfIIro+vg7mUMzMctD+07BLk+jejRHtPVIxHmNnZrZYds80ve5z3Xw= 49204219353786910100605282012781696579642953908541693903348594981245301165936599174304121350092894937817100350990938057159324959104937469442065996667276651025661016077514839755853073999975805394464570132481314896694678249282338429544941873047382467276103868995474424700207571657816852575364781281563515280764
+AKbFfU3GL6NILVyONPVD/X0tffk5HS//7FBp7n6JKMXu3VXvWnfTl32R0WyVHk2yP0iIyi6SUusSicOH9ncO8KJHmaoMGN9Fn+Zq94FTFqZne5NxHmCtwRAbFNDVGg4FeemGXEe1S5Kk1VcvWqnp+QgY0uwa7RtT8C7/T+1pZlwq 117110890075563714812929271250884717870581483065920538069845585667296154465072587148155060755111295509684258790280104272121160614620669593483929827848744548171793187278583947500205314283462739235860439216105116687015890394925743036369717346234391524403038196640934551590543386844279091801685432977718405127210
+AJ0xZ9dfRc6P4W31bMHBymgOq+38ETEIMvMtr+wB5WTcsquZY1IUB4IVkrHaOo3W2SIr479IfJOOQhmvyRS4iB05yDI88Z/fJfXarkH53gDivECuo+5+JmV7e0S6gCvOuVamwoQjlK3G32bCV2946ry4EyIsVZ6Alk9xk7X5HfGU 110384671994603894282707302829898242894456931176497230904862171369974466400767175784681299142670706023468915238955836087425993929524341269289746060546848852729416925808186253355106621584826213979718185296723694190658548757311188764342751280681935289121682174507629679900374674992438818324999211250580434317716
+fjzmb1D+YBU5Wn1GlwhxjiJS07k+fXxjeNRbOv5SjktzxOXmautO8xZ5ACOlYrTt5G2gzW2PU6sYNfByQ0xoUSyutOuQlD2r+8MnDrxCo6RxT3P0dUSX7q0IVj+oLK4GPbscnKLfe6KqUcYLMgKnDYnc+ztFD+csL6BQnM9WMLk= 88647261832601702291191332432291274285041869480562430895152086741320122435409959711452438332192792226899741738806447713240934608106883094466050154088410020909933636902495700779087737304255058561688767369900548260278700135161077055869478387490726087630962098228537973426295306997128615315548440548541717688505
+YDg99aHkQSh9RjytWknbXzcgLD8MrWUEHF46yQLHYANKXaQYyf3yGM9TYPCDUqWbOapqQe+XfOCoACLyRg7vVDsnOPRDI9ZFUgCQBNG06ZOxzktEhnNJoRC99da8jyodFqqk2f9UD1lVa8tsQdatjUDocwgJaDAOpYEyGnUlbXo= 67567767932654827067250684965667741848878457020992905661955722020937161710030993261011062929936964216357930453809610708591260182295097124272956485574313839759737390934220465669626974544253750900911093325004172643146669082793591441922014060981070503803266774197958528843445580649512373693546027107823355522426
+ANdsfO+cNtWsbT/QJHGkYAL2WCHWVPrX6oEz78pO8lUwiigVEow5roLI5Tm7GP7XffjF95z5WDxzpoam+Bfp4za75D6ZEHQmuFnpWQAmNLUHdKUE6UcsWN1rbV1uY+x+Nr5Vni/M7PfQi1yRTTJTYav40tFPb9rY48FsUotivoxd 151275723772668372472508916060743043308364940375633847663054782759325087560768667906829087958412643723335046123025802453213225972572697773468957759328009026531148112732519692142632237595562259864125679649273054426879080697360204352423668940795473103047320116317252295126635024518179060076282921965794883439709
+D2Z8YA0G/vzEVVQ6itLPUC92r9n9FKRpf6lDPWIgpZOOfIkukPp7zzTlo9Ej5IsBrZBbtGz/eYmlHeZ8Y9pQj8HFW24HeKYqjmR0ujbNxI0QgoE+VUwPVg0HhoQsOGmq47zpXpkDwpOAZbMh/t1Bafq6r2zM0qmiwOacJ8KFUas= 10814483230552506566705634583020057064935800294861277580077052473134972003523900930560478187758928889017740705417070994563709463926267126567504805864719383185267204810142444719634360655595490833208838383875687102074846353850310954150927702228780599083427768247170427544730791038729428517279760042619935478187
+XoZpSMHqlOyPYJS7dWSRNDJHCkjbo6+DECzu0FpB9O8bftcxan/06Twbo5d1lEqPlLx3w0XeWtrmCSCaeVcXVtlY3QuPjdKPv8LBnnhslPOVcbGyflaTPXU+ITWE6rwnIF+yWQl3NIwCV4EBtCT+3U//Dt/Ebif9gzfKpKltD6U= 66377743237695515693282032069691369056215169443985727092982918806809030742478033317158686828712146024066618073633406428345129492010236994055590530566431286733776441810601990431112187030942086686719669823512292071202675269428014136307286941704297995292544712278047959299939833088742083527714893795660235870117
+QUbbkyJQ0Nru9c/nPbphM6VxHp5DWlai6407KIDbTGvUReVYI7de1gO/BFphL9GA7gDareYoMuej3/SVp8lEujXywtXzjiI+j2TzR3YYiMBAMhsJO1wU9pxy69Cj5xeFFlrOycjE9sPS9nrqnEEEFNPiK/GDDTHj0KuNbWSCLrI= 45838919357034925862751142472777409057791233610959872523563363744902783251621354580995921495295078179996083468819097423327554678806691589090814275138081407920379810144694354354954459732280968086760894209634364189264517251735804373673532012530665557440070501687207620525228416650281363557992436992284712644274
+F+uI7ARCeAlnPLO1YR7RJj8LyhtE/EJMcY45lsNMff0YeENe8KOITZVxNA55FcxDYpg9sKi1UV3/ASqkqpH8MOxWpBdT2UwSX3oBkp6ETfJKqiag0C4MS8cQVsfcKF39BJ6KUE7X6KUEj11j2YIIRREmLPyZ0LatG7dN7Rmv2iI= 16797235966984072293396362937533957334369977688369659112225970370748312376722010874726300554329794854683394163379447263409228872034356195791733533528404245739693397078461712458035888813157166614479153484688995068722288153129390850561042173295997770817893349738328312152341860704179681230323810266038959856162
+ALkEoXznA7BJlBIfA3Avl9kygQcxexEMApwduVRiXeYG0uEXMQU4rgMJBlPqs+ly8LTIcLFaLnJAG2KFQn2GXz2TNa7w4xkegkrslIJEtBWX/lc7VzRtcLbhaXEs0Ci1ValnW9Up7dYOj3Qw9eNo/9M9b1fD9TI+0QXFtp1ge728 129924120553920201168632484268654219915712271781591182777925696006023100660478316445751842982460082888615429513674356810187315558964251402722465707617058251479494744427428152566665405423424700027316505872162698141109433045594670140335040479559124757490095995568556894332243767736124299898808796118800328801724
+Ki3FNTEE870E9GaNtbT418CLSmf++s6Di3hzAy8NgiDOFo+uuicJa54V3JNRxOBc99sl/chfZuaBQt14BFOQ0i+9rm2KD82okNABd+SNfXOb0Ow2taZX8CpkVJYDyphFPyHbPIKmzwMShNx9X2z9w4++tJgzBzGcFTPv1nhAlxc= 29618953883711174042338818332957726953262658484143534778541769862244883781157097499904047532839425875312731531093860721544220959674634750905085721866390609141599426547378130082409488797303960018348798930232014390380383063108812922828160584483043190739354817699497573863286563890071313017508437166939160221463
+AJq8tcSnAq6M32ViO4hVGiHY7Tb08cLVyxpl/v0Y5adYblvjrbsFcCmsNDi5PnBOBl5awR7KZdQ1xgq6jIs+SQbccEMvJvGUZW5MgcHrXBj9XVd+8oB0z0eahqXpgYBqLDeHLU6238xR3dJYFf+Xrcrzjg8swx66OmQKkAQVJtdq 108660120968150664552423780971948386965268856900017812123107864829782135741514930439461240950044759098603910762272795612101834680870627850178371693837566833495418727543557712057554231215186486008080050486837716071537742708913279026303380104388546316647349432118287628353129105425052237438199445863950767806314
+AI3mfrgcRwtE3mA12gSoQV1xyIGy/YA4pCCvja4mTjvzQOAfiZL0efadxZH5awohCC1SpZDCFsE9yYp4LugHKu/A8zMcp4k5ena8sTPDkSod1yucjybgmVJ5h17Pru28AzHQ/YUmCnojQv55aV2+AUhxzIfojY+NT2PKRqr+vuf+ 99645829268436288676280252226747461064597487404802430565833102291706103139410465131373666856042539909746769688396958963177805479987372681967013633920910376342526433530508868114301205524789149997372160919406352823342811006288909548557622230243808373083272214426118230701324879006645047374853535922112549545982
+TmXQ+D8XFKSclXwnTIH8d+sb1IV0gfm7GagJahaFL6A9rvYaZ0NTizkG5DQ0RmXyo0wPmLork/296whsdNdUxVAwnGFlWWvMV0ftR1fOvN9KoT0WtVZ4Rmu6Fuc7q1PskAZzIp7MkOAxILO4iX5dNuVC+GLZYIbpTel3Ga8fXuU= 55052751096768041533898435453266875315629605001878362193939750978427494147944918632414581744895066623527980497732722163665712245580312596487741856071020477624754815927936394948233480228964159047139170955663289543349257377302556035170334384320502468579367401821986660515827461352578142560630318492817238744805
+EF6KIBWQiQoHOnBdJs1p+WIcAv9ILt0cnQVo+o/2niOtI0C+eFBSiNgeddhotkQFgHvGUjq8BPYgtLC8A5IFKGzXu4SYj5ziagka0hqfhVs9zVHKNx2NUoMhPDG5R7+giwEGGPOayGHVNbsBf1FBYG91+mwy8hnNbhcHSnvLGk4= 11494909948912248031301686864833544028186348338729984264372557659364976118965740281229664413031002362633393381744365783802034700038490736736266032000546393704814403638058993380993275865674190555703046732456017652317200288968188655019374159412919163798248766655991273308390043613040731449231289437754791500366
+AL7wCh8tkFe07qChFAzRkrnNehvda/Teroj65X1Bmcr14+/zeJlZDObYRYBOm8YYSYNgJekcL3o9lLFE34sCMbSJgm4dGwpEVexiLVi+zc8ndnqBDSAnRqtC+3jbInm/v8l6cUvuzrUNtzXIQ/H4FrmPMiVy0EMerkMtkfw5GBsd 134080980697158076909534078193319899756347955848461100874771253577754225619652121295523443912922220564492468474647193062555347746840044705102003079330399499915801536721237211615317000955332058281901995149084303143543150689010335818219129745452688372571010816270728441637278434982752674030696337642893239393053
+APunLhlblRi3bbRBwSV8dsw8h5SvT8ncAmXPnca+e1dLzrQZzL7P2OhFope0mW1MCDl2kJPiGTdK3SiYJVsAFeR3r/0z96g3oq+8uS66T6VaJym0QToMsqQF4/fUMaTo9HsukyPyOgjVIU+6TiFd3SxQKIu1/GpQWVQIP2pkHFKM 176716779397275986910036615967409090183531310366246043951791503601618945774743601662530806467045971394247287367421508126613573039423674729894091424105133906122821596079925540513892022311039293333114333317886304014722168786051080135090242879622144693440448171583324154550086458411590240882982297314605229953676
+MM6B5AgdJKe5OLlPzcXwi9WhqQjx5KsnBYxxa3kWdGNTdk/IN6TVd4Ptn8lWkLm78mw3DXP4Ol1sQbIfkHRoKFUN6TaWg5aDCJBDXyHSTZI2FDc1di0Te1SwziYn0sIOe+R+rfuLuHlcT1xaZBgL6+dDLAZaZza36UEjn5i/pTs= 34273208848307582992498656582721015257885595139328466874135636009184357438445251703533153492315835793684794951576799764181908090765379592683793969576893243386892292517067596035059342970830813419330530731370385186653239446376170533147020072285887964430731437765184844167400169982662183791828762458682426369339
+AJK1dx77ZA4F0sYCgRL1LKSTvjGTKBHd4QBeVnE6FKJxIow82puqtsVZ7TBxbECex+LkLQPrEbuQaVr3giUDjg0aJCE0D9ZVXCUS06qulqcCCdWgGFHXDOQzTWDn6TlJCGxtTEMbMxSlUq1q0iKZ19kwMHiT3GydBn8/G7tIYd23 103022457217861194294329435482792508957642944252832971366936865663608381648431732294396977429863681671686490913575377744795372643599438468695483808375208871881849232129651519218503507811863794426234594709451104684234156597418383183271923307418704786548452806494411689822939919114966188329657999811363991575991
+fPZNsqUYBbVGA2FAiglnByxGJOZkVSpj8Y4QNW5wq6o/1e/PRwp0TLYJXIoCJRs82pAj0QDpQbHl5lCZmNxEIQP8o8xI//HCPxPIdgBJmSfm3VGetrOpqEGU0KJJqK4IsjoVpAfPFMUMOpGNz9CSvCHGk1AKrtYvrTJEKmETuig= 87751387019308584846595931543798879607048239290774788042055795835726250309378365187899578817976976035304304847968410200168743967600896348021636654074952051821111673620467434295067182213181329543946368332581250062140819766061014427755090798550122401239987766844126425179573454145697756278292448630509686471208
+EmT6DUd0bxcdprYhAnycQaxm89kltJOlIOGFFRmEK90H3RhzBGr5PRVTJVqemFVpVliO1gy1nPHgqDGVNIE1GXhrhyFJU6m+HJeNcduippRe38xPCiuraRkXao79X7WAiVYUq6RIH+UIRnfTvHBgzTwjrOvKJ5853hYmGaanjh0= 12917015385266582065020051081997430892582163827812227349569911846746592973268746845211126663077128575098045461893559476227689488349263954564361736197688317585888118974603264677576027836032271531903881104937422976121352854003385726888601980526287956222142458858211589791399646989299770657341412683499692330525
+APtOYyWzdY1A/YU0SGrtjPdMZA5E50Y3hJVXppwuuSk04TjXzcbu2Sqp7sMnKYbToRW4nB5p2UnaLPhTRy0yszOd1auLngW+0ttCybD6nTcVoP65gYOwXGfSEQysqKLb1OfV8kYq5Ba92Efn+CcWWWuS0wEr97W5M/Hccx9bGu0r 176473215292413922394356058789571494026727424839036665031567966488209592078148711908841964690807374236235612412767651029865069639786447019874344449598703213025389428836803984245755885691094364960118900160737925054803955567361126391353868279642836569627177281508980029006921064654964339077608785831304875404587
+Vs6bjpYfFA1R/QTeCfhMuZLZ+Zxo6wxq1jFZpi5SBR1LaUwAtOAj38OJC8L7zmxSOj/RGEmJHkulI3E1MH7P7xlWbY468/azfot5fX9BgHrtptV6Q0dkBUg7H91+tcxdbm4/V0HGQGa2rZp+XK1rO+U/d0ki6iNbsCsCR+OeyvI= 60957991334776853645581868230398759578123373154273044785333939425321390401088800849629483265841435899835570419798325123273632247193463641611211088549152950252041797959644227170492417662363676228611376046334386877555777556575818860902071813120592757466883038430756577949025778080997296219236534786815367760626
+GiauT9A+wmwJsFbS2OPIM6ultIbU+kT2NgACn1jFAy+vNBahdfHMCH0jJdCs5TbmKTCeiEf3ITc5TV1OSvIejJ0GRkTf80nY47TAhiP1aehZvMAv59NQHHTDUE1U4TPVYKIyFpm1V1A+JBHKJzuGrB4lvqB2ed7k4m/ZD5lFLMM= 18363925023885496669420377869542744504974590667921570026763131637088916425434675950812384919000566852243714758512996458727914094904422651029609645299422563453163291342992902510788457007623888307499601267675322986672697397389663297565071582648674012080122614260400848960757021864980761735684874056409664531651
+AL/9KOZLtZu4+ZQYQsmOgbST8F4RV4N/Z+l8qsbCFlHdXHqTTkcN0chsccE/3KkVTZsAnAyJqogbAvB/RZqttaK5a8iKlOEoerUS92FVQw/42WhsVaFggR9cHVuvCD6QqclZjSBQKQzUMy0YWPWlycAZDIv96tooA+V+Fk0jbcFs 134819194171226950171930028888667967094069342154233489571728632904658607624703819928943642011918061760802468868660586005724399808048609316802502143143910585363214684061242274402109137825176291816945489430125510625857564490981683683589784133305376252294774711594646923226452625156299996630452243345104727556460
+AK5x2N/4+PKlsW/fNrw76CnE+nS76Rd7Ugo3IKhMTB/IuCc5xG4MQHo5MlWE0oVkZ+Gs4CxUpvD/WCCjHHFlSxKG4mC6ehz3NVLglBt+f1RWfPkF28JPd0UaIOG3um8kG4J3JDN48PXOPP86A0H8ZYbE5+ImmXsGAcwvScUQRInU 122499245103202714319465533564374494931278163571999934877854825659720649344163774228004853964635693562785966889622928722984134944784141208867445419597834322541679973956606275877526560988151196822256754309120410807075405427166696093800381410682490767468563176131997424692783482903880902119461752084196789357012
+ALZ12i0hqFhwRAikcoahYzH/BUolhgZ9Jz6adLvvTO4wk6LLOpNC/zCz+LjM7HazZomT1SqeYJ2X+WeGFLADHuWo+Gp/I3S0UEneYHKJxoU7OoOtE0mB0BCncLckHao/LmbpnQpS+Lx5bRsr0yE6oWNea6gbyRm/R0to74MI3/KK 128128022342420083856194424802390993133863171077961467523372211039771843125192435716337829530528063182315478279257832480290950255315151577221042903861075751839976362752440630888566422581799720709574650482021111126414843635330535518992034746102956214991673417580508389225948159518319625680855827280146399752842
+APXxvLifWgehdwdTRAJP5KrchRzgbUsyMWKcPGm2ZkwGDJjoTl2LIOOGVFiL4CyPBxahkEHf0nMxBN5oNGX/Y4W4PuOAC8gMgHzdLkPWkpnTcyoe5DD+fQsqNuKVw9nvyB15fx8k0d6b056nfFjnnRqgybby7MSllAWSKRYRdxVm 172707950911363219032118650562553641123743396229371815589867086054370029540557395298194067635069298952836929253340374819975848769009260895874615676938511747311585257140973518651959463416682165208985512233703837931718385346209362040743041262031997793519095342415901373534535662377972036003546589624834285049190
+O+9ohtZ9SzGLJoZM8IRQAjhc/GPt2X5G+M22ZidYjx9WgOTrZDXorSyxLuHxay6djsJSgjxYMj8MuanYSn/DzPWBB1Gn4cDmIsfeYuzO+vUJ4l6d0nIvBg9Iqs61/PGFd46YxhnDiVQ9HEznyTjzESnNqc0+/OkQVJcwNHAcZBg= 42087920806448980363073662127262313840530298932643042322138035915324224188032438119079107631420338701086802583985117830416851550991102672642532160807467909040086448764318690465254898516502941122327185894900817634110254371864896139724173087625913998657136384357741816102965779105122269429701537815263708996632
+VJOZmvqrqsIUTQSSJpZPhbQIYN2tsfBhAciWnfAYpwjK9/ts7OP4Qgdp6T/V2EsSRPnfZ0VKdLg1CnEWDhfcODo+/BZcUrJ0AviFAEtdeUhoMSWXtjel9Ln2guHY4s33z2cN70+e8gfjes65lCzrxUIXEF4nKxzKBnScoooQP5k= 59391682001673484862915842850714742391303140646889359425353339320546979084250010101273851580028171449840778038774656177449549941659895629203970455580974953864068394275066532699748911169800076515776388213090834432354601344176559839798153004796057709798368011673585434643656820656931921831615507416411999846297
+FRyJCOgPziO6RDHX1JgYGZRcSAuoQFIZM4niD/B0twK3l+TRpmVigKZAJnZZFtmX+0JQkDwQn3lcBGQIL6mgy+j0hD58U2/Wd6xebuHSzf4OHVGo1cYoqZLplszA+hVCoDVTHi2YAZ+GtfQEggumcNVxqfEZd6D9Nu//hm0t21M= 14824975573460749317081504809641216868382341402512168178334301409725840669112911061147252565570697788806398498723577368905065980113760265945344671897779830912242224090954834750057278285419880820811348943398148063418809729356397202526234113316098584002071850758705282845646489058224513019380757604894853946195
+dUk5LyS7mduFJlvh5o8R73kJIeeTh0Zli/y3XjtIXfCaNRf+wDlD/pX91JEwsQ5Mvj8yq/Uq13QyWhoNwsPpXVcJtJ+02wtIn5darsBDfzcD/LbWhl7zTRUeMjZ72gAWi1djx94SWjrZJS2oWZU92Og1yOyKRG+ua0AhHfYYh6g= 82361050315899968537319599868832189063658136463903643442673674137187842597528653416212822014359684261704550279153006971937114135373937934986951573613797195556144113400128502946618028800530164890707031379614952207482505803377774320259789692177752930767589642007257364960987343146063216186985472686575891023784
+AI6rejwEznR35rIPuIz0CP2aWyhRUR3unJ90YfxyuVYxrqOJQGSDTSf6SGDDw5MqpZXa9pWuwpyrb6smOq4ZtC3Er7lipJfXDjhy+0k1qcfMjmqbATUscwXGpgW+MO71cttccEz6vhbjndi8gvG5M/vfL2l1jA8nXuBd4e254dbz 100186164434910864539376019601151338080943067893748898987236087770762310617199833479771711726248130012472861788210345311298499515751355424063761182369333224929721733015910055321263016834247318907562652286587380604998130368845939290804442878127169587599285040969551065995197981341260363722618429042861484922611
+AJ5vLZX0fSs8dUSBqd5hki48T9cYuR0atxR+qv7cRu9nD1vP8uNVR8dLitg3XH0RARt3ZmOgi/AuggZt6tTxuIBg+9JhBY9WW+BLL5CnYWHC3AKMi7MQBWciLtmBpyF152bDaEcV1PXxtml2KxX0Ba0C+hGVDmJSdi8Kjd4AkfU6 111256341508463539324514225759801553679558662737345522765042612717818066374840372549356543720386819501973783940451033901079765311790026584654529398345993992144903839534037331533660672892487693477412528974248713261092693018326068480417183236210881306241164169849090833681510163753605662526243408192127670285626
+ZhXtSzn1GiFfHHnSKUYZiTcEWqlI8owyCKFjCQ+VEvkdk50m8uN7RCQ6ZhI545tN7Uy0WdLstJhgJETBYLHHIoWsJn07mgPxuyO0XsqNroICMQEOO/YWQFk1c0VqZifcohQAwJj7fONzM7hTcA22/7gVigJ3iLq178jZOJsEPQs= 71686982768953132894579286530164112027530221141251507987469672039995314435159469907420372652392376452531392493658576814100773556880394271726970628960571077839124343525055625420896355363707908511865700866168843075071778015504724409171911254647909938237551680861008772396291072284353858575645679153885560978699
+Vc8Cw5m5yI+bJ5sUJYm/F2wyZ5x3D4ydyL0uU/3eVF2ZJu55OOlC9pUyyv7WGExClHvWpR9mhMnsqCLyseLfM2Q/YXJ7cjGPKp2xd+fvwHa4hRi1FdOxs96rJnb+HUt9hTwQByXgzpnUfs7AqrqaNf4WSlBNMu0IOOqDdB4iVHU= 60256873326783629723455608618518793848697944184579877638436234491615392142659293975260290798403892159720925893207048153291000664050780029732557737984085196691225472664027706406879051455184548871511448456651238810812870905640934953489289909009741493031472382758586341375517766302753448531830002512912250459253
+QmeUn6cbpE8YrDfMETz/+KVFaK+d4NHHzcdj/MnjcmqQSLpP/XwCW/aeudlN3SfKd6rNo1XZefunZO/ek+PHEIy899WzjiJaajhf2X05fl9WuPEaMES3Yrr+ClogFNQ+9jL8+7L+J8lDuqQzvchT0U0RPay5HSNZw+ZouVCiQ18= 46630904037845609335515965570673490721137364238213103678233212262384415738654627185220187275286458759154841820256007930773120637898228224906635911124921895934056288121005350040349882413280772888907627838315559544636626856478316691755270725623680935763476199888127096014398699432042227882284223578563208692575
+ALUBYIShA4w5kRUa6iNF8S33DqaprdOWjVBnO+j9CCGtUh+NNwfpKR8AKf536MtuFFtwaQvRIlkLpaTYXuRxzyU/YG2+UfRQF3pEmXQhcMxJqFzqZ5nWCIWlJ/KtYS4lcC/B7hD2UGAktnIdjVUTSxX60VzA+zxeunV2iBZXQlEs 127106299687401374061881872616647348819431126560557369258073443762502337592227172639640997680536372567116568811258505773087926491911004324918919511363985868314578663758269650473780772688462266790559846182685481907703974916356209771821075179827563487466641669110315430790405454641953880582274165368514679034156
+ANyAdMnVCVjmUZGiVdyvGE5mUQpKoJOJINqMAfzVUGvvxXFmGdoAx+xsDRNAP4KoijpXk6E3yPBPBZEWyhiHnyjEkktK/gX6gnb745afS0QIlsjhKCk/W/BHXkzC862Llnc1ZGAIsERnGceEoZHdICfDUh/7nMFp5WuSMzPB7nEO 154841617115465511611746667401422322067517612306328612547616471923266281876818466022676728696273611923942543658633762267658490816264271663863494188027433799849037906883352478212451733963905925106470599843045599411842850386623187980045961158399934160107237440980574028985561404965317132715808604373199725949198
+AJ4nfhDe+HojR2YrprDHW9FVUxsZvoIekwlNL2iKFRFcTB9IcEdh6QnGcaRinev7yEYUsL6saSxUj39uWlqo8udJFdszuuQUmnloIi34L5uj0m1OpLy2dawpFQr8pqyA7go4ugMMj6XCtiVnISUcK8wjHgY3Jed/EKK8k5ce0Jxt 111059703393618496515021583605572584329116596402705082562306930876194742195701060137568030171429700588269665205795898835699633817098262654446852249498668467827435829513531633390969638488553144849154126899372953755511962841193763362947708260103832329116485114451074371844037650417731807385491783373627950406765
+AL+heSTflb2MkRYFTKghfzqlVQ1oE5vcx0eCIsy9NJ2NGFXCRRvoGDVoB8UEsUWIRnaA+MIpwDKGpbOS8kRQrvBvPe/xM/t3jrGkaS6pN064+bCBx8Y/Jq31ZXNG8oUol+y1Eo6fkUKNl4EOetmZWK8VmhVwol5YngDffj4Q8ned 134567692290185631768518572983694048149859804864902017394351513816079806629664302312927579302025923096596995134868068794900003728293470554490807959649153000914807604036531509869958441069678002226922395630284261949256022972967357884468325217602330254290548618134453007903724438628204981673400911693835033278365
+AI272d2sbYIi637kHZC+6lievgcDvT5VKaCnus3fHwm2vfao7oYu31P4st9DlqPWJ635X6QtLkU5HgvVSy66MDj2fcOfwVL09ffkZYnoGNdhMADVgOq62Ro5cCpOdw8Ko0cCyVpVIaSysPuqY7kiClf9GTdyZz/uYHDgwWeNrc4R 99528854246023003959943182132914587584844397870416002887630245681136432049666385367430032197518895755482367603560037194955739661569172773017279832774100155646116233705958563163070414171045438199561777058338188494271322834524386565519620661180246416329082614115142485663975718653564590519408413408765689056785
+AN9S8vPzo4SkyKsk07nfyD0um1riJzRqqWF9KCL+kWMHajurgPACikYzu61tL7l1mNEaIU16Ndz541o+y76DgsTLYszu4KXUOEt1Gu3eHy05Fq18zCDlNesSVjkZjPmuJr2ku+p0cP0TLLMn7/KuVOm4GlEVc6OvBNZuEzRriSYZ 156823459768092337875922818543729136404805918580285507923139232733465414368775678369646914249412830351437211620056021568154043505276475345347569200977945836210758870414054407438380975491139001471954448623922841964684437333066353208837709613982022690623722155151315252634380695513434502419141555410441456920089
+AMc5H8kywLgiT4zz5xgoI90jejsHorbqUGtBeX9wke7zyvEKyWxRKScZwzRbinjDZzN48eg/30qTZOV2Rw97JFg+EA63iZ0vqfF8jErIt3hODniKX8zayCuNmiSb5kiZL0UDU1SNh8ER4m6o5vshBKkmqs0PeozfCGQtR3bZXlx4 139899247405256530335276706333424670310599977544642091674186635734421385499036688803073040921114325725234673132788498809189814711681909865484671959982394306416477300458309408833281654917008031099378445580498219376391819745965887864647387211647794422908411100892195529730435423964537342228510107659017578765432
+AKv+3H/TruTX3wdMWnLzD05em8u/QMl6lCHT4VkK+uZwBXoLeji54Tcs/hZIhj0Bdj0URrRt+7JdGSTy4Sr986AtVFxBJZA3lT+JT4JSrq3oY1Tv+tX/yg8ZodQmbpQyyfaFg3BgeHNmsUoCrdqhj4IwBqEXoOBRIXnzaTuqqSEw 120779384043726135670909127168686589868907326577918074234323699599475436892003731971700278391108690400460261929381703781833059801757700386671579819341589048987186473249926590758009001670959004477454905417357202448886738669226760846888369186457452643053236389556969071303251275912453385963613554945645058007344
+ANXIB+HxOyJd3YYsscMpqZpi/eYjZi5q6A0MohU4BiWEJK/E4uIObLJDH5yd4ng+hn7UMhc+R/AxG88hIdOc5NyG/QyFs95ZLUC26F9rkRifu2CBkgqR5EQi2cgwC8jGxQOkC62YND6cAn/ILsKTYaH0iavtO9Tz04vQp9Ypc82H 150122383481070201614242107655752525590609186454390549085509458064289390813495886095936526832230958746095739308601699615024239939948911472291507190108935262129646691795733786714291498653838550751365834947465294261687773081563139416397262227609481906371677917295227469553787085145970923979142676551778927103367
+ZQLFoW+dJ7vrHdMlcLRGKY6T6PZKnE2L3NjXymS/55my2CDBLdDf3oXwLlRjVt9KnEiXyQzLhyY2PrFA4k3N/3P5lVDLHero5c36TMshbHgbIKRGN2CGWPEFeQ4j040IwVbQCPJeuF3jL5ikCxWZFXfeEnTL6TqumLfD9yLQfKA= 70932215714423143395949105745758445705072524008235214324766464113352968998429901322485575506330607802260244612268338586532462314021433435523464635419846126736185176246740838082062856583684393425704173881940108783636582561707441482446854068022535943408999200681879161519209676205165680598258447492092651404448
+LzzvPw0FdtM2G/RRiqoajJiIH+Lw3jpL4H+08yOpp1bNITR2Aq0beu2nP0H4o2Z1/FNr2hzuGakkAhVbmmRXc8keoOkeaAQAP/8OYxHpjrqou3WPWaKx+vUCTSqVYYf8gnVKpAAC2cD+3lW+/ZJ538o+c0ovbUKNu1u1j1OBtA0= 33171669664542509840621265032202455391098253465550501094201777336478104142847268103467889435377685359857979277521589539506627375165485879405453566052091202280471235979376217319335800766353336252760793484157724210008639813552207624049019149744883918494762511376489708611103181576211531366514802868659603747853
+APrGj1lIIlxA57DNh+bTEAFbJK2Y2P3MxLShb4fPx2aY6j88k3umoe07ISQLf9PzNPeml4/0I3w0KNd2x4s9KHbj7NsIT64lhO6eQSEteqZXZGXUYUyNzhrTbAjt+Q9LVKItQhsTkTW2HTQ5RQZfGrkL118b/I18J4P+T8CGZdDz 176100632478477421621142147788721746818712752858710594712903769452749028606541677227413333567013253138397373757811889654342173021761934591400685421771460440213093509170325205622261487145789848227404883040799927313402244625239515162996390018403365063394514244196976794479529075569412676472840544017222373593331
+Fvcl/LemWk29I5LCjU1QedTjGlkvFF/kZXNkRJv+vNZ7qgq6pX8WB9yVkk6AoclDYAhCRfKTKuEpR23iafVuHpprPfNXcqBH8n01kq3U27xqIy2hS+D6BRBK67PQaekq31EB0aOcEb/DuNaXakS9+mtTMx6BKt+WoEY+NkzHK6c= 16126868736093163702771491576570380743773057522016869811780571865928979861357811080042796140032050364543242385458140594532945509386155523162799601656485075247603490060565663264947465987286983338572455184901756399862440455644131755848583379822279676555143231305246033911608913609591095831135803702269767527335
+AKW8tvaB8YZ7J5W2lmquBniJzUhRfqFdPZPqvBoMzR4cRh1CMNdSFsYsnsaF3KolNzogdsxFpHAaEMG6zSvpNJAoi4nixCqb5SETXrSLASXvNjI9MvCoE2JCRq7kMbjPL7cem+mBPWZITGUI6KVlJPLxQngHYSFxukqlx7jznwJH 116384596458828069344020651216200368975621068920641012055593076864629080375946542748377736186556382088448816531408136815533164209947323588157210859294774679831647934533061547276394884474877353537242203645373945111105805934070657589374883764420038511061919092743520704686962593876316976299391579463759429567047
+D5N2P4FrqDf7/2Z2BJsqah4SjUtolic/yNqdNzvNEogDKZKAJyGq4zhnHvkYXkEm2ueU/FDPJRqisszG0oULdU6c7p8acirEwsGLVh4RamnFRgmQSK1vbiYB3bR+P+iFX/bZ+TWjN2Y3YMa5UB//I6Zb5kEIjmTpjY2LEPI1e6s= 10937855369372570149476727082965401421189236366492771695094788039313362971972373068736123833330006002198346944149230147444718818161877123407713821100752433128205189334393732633989950841577315682292180735057952587083688644195300641998709155269462601925653013312848413290208844194513502358901613104779186502571
+V/A1ktS0xrcwlI8xrYqvlLCFYrdVp8tEzZaZ9iNNpPH/pzVsA0WbnnUeHbdilkje+4OdoX9C4U2xaOuWOfvqLR0c7GeCkSffCqyf4ZsBmjy/BQL6rCpxMF0gIHXO5O8aJ1h17hy9LTuNzWm4zVh4pNFuHC9L6nAcf92udMiIQzk= 61752386563628388546439207444896778638632243226541303179646524864765343154194512297447627825411023405896612559648434895675553567405277169056807223959390559391191382555701580549902639604424290133917402316755076644943742815711432111554988540913643347167948778404861099845961151998728662878854088239266688156473
+APoPgEKA0/r1FYmt/Iso6ChYK6dDU62Y+vH5h/LVE00biBYG1f7aL3GdllUTN+XQSHpqlDw8CD+9xojwZIMfgpgjOwLbbe7Aso460zLrg3R8aHBpbVt8iZUgjACwPYr5UyKbFzIAWaXcnYYQ+tCO9aDIuOz+/7eIF62C81zXFJVZ 175598490446477604563905754135475294999639698464908622773037381109011373179895295130424828038708319325919451724985361900259676699137657615076219968061941008972496322083528922054390781811699677037439989404270415929836486610353098273115864435328533577114470407444852521009919911888840405368858409835197558461785
+cL54ymLJhRx3U20Y9aUTIsXy9Ags+XHy4qk3F7uJyO46eiXSL7VrrR9vTQXAbETbu1YiVWfslsPht810eUDUVaVir6yLnXkywn46Ci42FEvVoTEFjO22uYcCh8nqB8H589w/+lVSlNrcILugwfdfCvK1iZzVimOO6l3qzfXToOU= 79171550718114578361958369278761819285111811576818442980166457146638966315793211967882077899426611721874954146020093740153495693185472340728106727284441726113022873005252623222594060645383105757498856463065370975867121188445567981809371870213273555432308279508351518168027875538720367440153667708369625129189
+QdQN4qW2QZq8/fmSaqlRiPSoDbhmF0oYjaY29HcKYGHdlOH0AMJb+RUIq1aszvVtjh7AYay2TNhaZMWQ6Qi3c42SNk3A1MVknT6zqiRCGjNFfxf/matbRLbTFQF832MAId708vrFLF/o2HpekMkc5hcHB6bkUUhEI1NLcMXwGck= 46226230186280253581676626651942823886592433541360244612432763620730826574920825070086312767146345247802570752482654580909236388357139147786783758670999083804670979821212991224400629053427330483809790366665043598754931511997925850227997764381723288657884346974360232490075739442406431704368767588177525348809
+cxHvCK/dyVDvaqCCQyLeaiBGA36mV5el+1lc2eUTkHGUzX5gU0QQCEp+iSXNJhIOON8VFpKOFsziuV0Z+3cegWRw/VnxnjXcBh6IDKdupzOPB+Yl8MA1ti/GrQjLC6ikcNYNjQT0ZThL7KTqEvvZJH68WYmD0IuK26swjNGIGaI= 80804939616399473443737611589382762718815989847332356984276911837267997590368701684135326680567847542004499684038240485603420973682522792156533112356849436451918522884749244246467852622918805139990256619014116276456718693703261686778030658826952213058982142604346352178078750879100976710761147710018148637090
+AIQ3OIZevkYoRGBmsFaXJobSfLeInuKKReVYNjP5VEPoMq0mXTltY6l09/rQ3d1JjsMD1PfA7emhxex+H9t3leBIfCi6Ux34GQEjXWpQc4awuiy9tbR077HaJyecvb8Qy1FTnOHoH5C043QJzrKYT/sFXjgB60piI8Y0R/hwxO4r 92845026347218330987427785323244729176754623818531419911990153715676845614711324345879159989637824921793015074978358052562420379797956750450245721653716740651389924718711940869162230097839047895842495414221110468446944827052871968998907462191349838598297775847512250220907563815783358238473966349820476321323
+LoG6ib5lUh57rdmSkZSWzBoudytFohS4uoU/uly6OaQDOi34GeNVxu/yr6RszJyL9JWkGNgFaBIv/HirH5zA9VQAL/6kpL93a0/GQ/nuHkHy3GWZPF/2+yJ0PfazQ40fWhHZfRxBngWslbguFPjj1XaJ37YzpQAYb/+QcUai9ic= 32658152290878644668906121702816147999633088014476055330179597550087921141413344679134407016170035735846077181424615228657687216737432274043674411132745299610950657139041836412322040866250189120286839287690983293111362228893996267791120043532014262644480689231457941173330523718758287779526551822788227954215
+AKu2jgOQCCfYZ3CLkXEH44aO4TtwMPeK/eq4FtNj9HZ9FxT0LLNJh0ZXPOaPJjgznvIw5C7/hNm7rUs1JeV8I8dj3nbS3EVERQz1gc/ckYB3H1bViWREOD5+TScDusi86YO/z4ar3dauKkg5kT1kKDuU/OP5kNMWvtJjHc4Vd3L3 120581042599355202025471829872601846477331097842315143148145881424071317426176264583672725691485724160094190478865850305422057632110749683552966861219554215519032344086824849470294473808177223497912069335635933312949412445851201918768630656712413082629164792850095444166888072453190903931430551124946191872759
+ANLs7OsR7oBM5jSjVADrk+Mx9d0TeieTIkxwWiJ5STKNQmW2EzPOjgbfcLhbYEhzzDFJveXO2dzz6/c8V5oW2yqg7VMx88DzEbpQnQpk/rOQRw9jbI4fxXNJHkNZCeysEVvFfLJb4ecsGA0xJ3Aylny/jP10ahPv2z5K99edGZSU 148116916208650944522110872759145096907599612943009577897396622287067669897712748449324334650112672914917664881091633448764667172850435775162090891556266912697811031318228334453406561952979778127173704706529448647577013482442758465809198730066784986763500579667100246958959793527011919373534159474250508506260
+AL+Er3n1qj+SBsZVtOMJYg4m0CN+DE6gRnC1F7nPvd2XnBe+QE0+LKfcpUDHVNxoydW4BDzNVwnUNbyjXZ+iuddPtO9hchVEI36UiuL0ydeldFpOZ9mtHJaAF6abd0MlHw4vXRf8CbOvXb5N4s76ggijlZBjRtU563sSmBcyq6Zt 134488725667189507159811764480908602790838430340670328479145818969651133017546803581865897303917708192047926432630297993507146075655594931523561067937580218599890162311074002344315818494246433967228889645359283635389151927472221799543158424012020308449895562192866672439712148770104592027035768027605661099629
+AK/04XOBSjjPpuFXTDF82RNWnKqZz9mJQbS2B5bn0ehFnBa6j+B+MazX+AxXTL/d5+hPLT1uexcnSMl3DcGGwKipOXg7Dtuj3pfJXHTrCqXAUYrIXI+8vKVQO55yQPGfzIg9SVgetwW1sDk+a28ZhJ5a9OddqNoi5C+dLce7ZtNb 123560902006294001923570614486104726169564351074482936927091682096999779538353161007361361829586988452098646362280351148131540524964916445100589671458589346440250329883789099771417949746709217272531950438336245613419967556433467843237384555807236658182067742367748737224684334525934210197178231424396818830171
+PzOEGHlihiveoWFAALY+LOfkRJfm0NUF/uR6cSU/tbpGAq4onNpr+iZIzEP5o3JBLOtDC595/NBPI0fzaXl0vQvgJs6KG8iKANjsLKQjIpZBkoKhdbG9MzTVQuAeuDW0w3sn2iMZ/v2dgAzRwfqmQYXJr3I2BbcwWraIJuZXw5A= 44381416070253681813077725822442106641846565789204187691647505370231831464947935035197059366680327425453811558282831465960889061956588244308214943856009686127871667376028831540813257349779756631357122923723235595360268572998278795110672666089470210929411514949652537714634611421849780859192966935514197771152
+APnuduN01GS9dO2m2uCLs400AR2lX7elOnIPC5U6e17qbukxWYzNhilZlM4kdGXAIeYpzFdSIW/gxRMZe6TXq9krFWRaaPyT2QwRfGHYnazS9F1QNYmW1zXdt+qVp0JGxmh5PyDstbP8Z3x50/E8Mb0gLLPhNAvzY2Jnr9A8Q1Hy 175507868985304663005133968393406051624825489142498103948374797086106732382869120248515993626061853699363294022457032257026588816021007648668265488426495800459085474654859258116280251546902009156490112550154951965894022789029787886785376415437170872937201839249103828294508088966180386198213606090453461193202
+QHEhL4iVzNdUsfG0izTEepwTOvxka8t/9MwuF1Ey6kxsI+ry4g4sJPgR2xMnbtOmvQn2NitAkfvA8JPCiL7a8+gmf+DVRDjKDfpfrtgAVmo+3rH+uJYTrKhAp8R7ggU2xIrvbIrgeUj7ieThPI3Rtap+IdkPCL853JC/+oKtytM= 45252649968839515171157821292772647085425694172492111870169593872127007254353374581972876464918186509502070064028725519394859148593053614163356612260257013360168930649423732336969778875205250872728821432415158634190866775855521719727700464116412886964736859295086745723651735554245035077902615220578218265299
+APeaekK4mVhEShCfM0mkRebcg1Iq5CgrFIEGOoh1nHzgebr5A9Wrhm9yD1Vd3e+fFD9urDRB4y5MHPJHX1U2NFToC+H8nQkFXL8bfd/9Wl2c7y8m0Mxwi53pLIdzETLbbfeOOtJvuSYYT3n8+/PeMnJ46UD8OfqtnFuS0/bVpFLS 173873040145444066957050580959132871919216036714423404143335635770937773583761934638398867981658394368476005882852706046614562314432695052874974848076542261910660410561876043187368112065303981001507235893831108658530338308496461162623683138693880482650786841100027392293758260448606244283355655751440485602002
+FqC/wgZDPTUoObPFSH5w4QR79zj/O+ZiHGTEnsBMwNZD3Gl/ClRDIsFMDDupNLgwgXsqCQbpwSOHOtAvUuAFwRpzt5B7lwIgtP5ism/AZRno5p+9WVSmUAM3glHsNtvYydz2MkXtnXzSMIR1ZVoLrdwMnckE4pbMzggqz+JZqxw= 15889870005716350976759704672045310928616256175405784574141006779373730686049218680335525720670897894546334915362899913262232170795516176419192840427996647372619000239408311568577050460995518058850793096827271653902583271225799114408537346367483775593212272587811309978019791973449354003275559762102731778844
+AJXNbv2AMWadF5h99ZAUy5gLnVK/hMaakFo0ZedtPNRJobxPmwj+h52G+Czd0U48G0V0wpdeUJC9v/4BhjzhCvNhNsdAT1+vQXDuteYQ1aspsEKLQ6b+NknO88QSbRJw53+KeOY2xe7PKOa4V89XnFFBF7wljRnIYrM8vvcqVQDk 105194875227030598769888785590198577650278341586165110611689226597424766274486797264032300493674927704016605741286512271390703088626381669060095573361828932336327125438452066548897528158329044309005232090053420259033538936293519762277428283316506398965916381374819450858053512398634116052299066189424983605476
+AIDRnUpBHepjBqYAlU4MG/8JxzX1mPxVNHpWvnEVgvqTQx/bisFPpXrYs3jAKIR/lzevYwhH0K/8Vvw4NK9iTMFqgSnU44AZztKsoxUXsEsl1UU56UscY5C7ciKU6vjjWI7nm/uHNOXdE82TQXkk2WX8ferNqZU5DaLFCb+zxb7w 90459642084794142567976043425270153270545560059973413835786695756473295513758287577749768786155290305189883600338986370836806413936196854410098516254596146039255388020628703824195128439558127783534033672712705194483515442668075394018677699876614329419492391568463215822656901183478205197671375262145069825776
+AIdvVNzJqWPgAShvi3GhbhMQft+SLigKGrhoqas2Saz/bA9u9Td6fAxa2LjrAqshW6cnm2aalc3Yv6RW/Y8vg7Ho31NSaRjT4zMUenykcC0/Y88UNxREi85wdnHwGytms6Lq49H8/7EFGJIyL1PLRWPmZn6XFkegscI/HUq/hiKm 95105613103051650721863964216778532448106311156426028879315612217763044797186635476805213120469258258125661666950525364331551671653846368977016286153840829836509696804585927581668281228810410814602664419962214359687545209312836366693384158782798559255789953908588601637765910472073600954502095647132310971046
+DdchOPjXrI6lpV84IdKCisPmdqZan8AARXRLADEhixsfXCYuO+WhNatI/fM1vgv+/TxwwIQjIfG1vOZcB36JUfjHYdItYQ70vUXaVFdpqvoBGyfOTU50Ds/11iGPCF8mWiQwR30/XAXytqDZtaVJVWsgHD3RigBSnSHhnvZAWYg= 9719024770319024562623340689338530708271347986326272393419504304391837979619189392867902307307106771234732135400958362219711925045600118964223238147375808749507928768896918369395426933218443166133187066167663170936604731896932630589251946733237697936733924510107175304126061649311812536190882160340308613512
+I+Z6rdTOt26/v3dtUP1plITb15fjb6aMDvqFS3AD1+nxBqnnk7ISGE9j6dv762EIWQpMzcCG5NCCq35KOHEwRXP28zup6olOMt3CBFgYVcBE2pWOpGiO19G/iFweYZXZPY5HgIkex7HBbb7l6HhomPc2sLL/IRhh2oogyHx2JMM= 25210054612455888156900839678249806510561198051210010474517915819801056434402727631042894881559517808906460418029149538469607239850657781476308872923928122553395468026744382526167194202058040459679991391557937527079948356545086684521068912222036707113005006607012596093923970784177288565193670152033981048003
+ALbBoyelCs4UkfnPjMT3S67ujhBHBEE0uxLx6kSGZq2IOMU/QdWYPFElRgYC/y++334FSEycjS6NAJJo2ITpZCO5AjNJ93J3WYgbDLiwu1VzKHX6ItfFNEk45km+QTi07+pDKcKNd1k0mxqpLd/PuZd5hRpPDDoKBb6i+mrCb2yF 128335905497646745013379107761994003743181143126608677203818152878840562628631384684712779135591095534911406031545494164782375276574093777950840330452805743803067864740000758175436633463846967335728314347497013853264454015790847388463800323796888198433722196292529074568758149650782323407298620158495364705413
+ANwlxEkeqmqYTxw1ZwMi1v2wo4ntPaEYZYoTLTJQfa+kuIksnHW9va243HAiOixd+rviVdm1dEwzESBbX0wiJNtRBpP+bnRxy4xOBjNoOB0c/tfka5JVwu5eeskyHx4V3inLviUaj86Yck42n5NaJFMfBvhzVftZ/YF9WBITI8g6 154592850289860621115358362871905683265658659789986179554827712019629689749439795961607030363152337159590319622241556795951071651584979664762468782303706550885785493534656062553770262954861884613383561063525714923031691298088562054236178003658891902606245782350998076658704876516153027797371814038658244397114
diff --git a/vendor/gems/ruby-openid-2.1.4/test/data/test1-discover.txt b/vendor/gems/ruby-openid-2.1.4/test/data/test1-discover.txt
new file mode 100644 (file)
index 0000000..7ec9b87
--- /dev/null
@@ -0,0 +1,137 @@
+equiv
+Status: 200 OK
+Content-Type: text/html
+
+<html>
+<head>
+<meta http-equiv="YADIS_HEADER" content="URL_BASE/xrds">
+<title>Joe Schmoe's Homepage</title>
+</head>
+<body>
+<h1>Joe Schmoe's Homepage</h1>
+<p>Blah blah blah blah blah blah blah</p>
+</body>
+</html>
+\f
+header
+Status: 200 OK
+Content-Type: text/html
+YADIS_HEADER: URL_BASE/xrds
+
+<html>
+<head>
+<title>Joe Schmoe's Homepage</title>
+</head>
+<body>
+<h1>Joe Schmoe's Homepage</h1>
+<p>Blah blah blah blah blah blah blah</p>
+</body>
+\f
+xrds
+Status: 200 OK
+Content-Type: application/xrds+xml
+
+<XRDS Content>
+\f
+xrds_ctparam
+Status: 200 OK
+Content-Type: application/xrds+xml; charset=UTF8
+
+<XRDS Content>
+\f
+xrds_ctcase
+Status: 200 OK
+Content-Type: appliCATION/XRDS+xml
+
+<XRDS Content>
+\f
+xrds_html
+Status: 200 OK
+Content-Type: text/html
+
+<XRDS Content>
+\f
+redir_equiv
+Status: 302 Found
+Content-Type: text/plain
+Location: URL_BASE/equiv
+
+You are presently being redirected.
+\f
+redir_header
+Status: 302 Found
+Content-Type: text/plain
+Location: URL_BASE/header
+
+You are presently being redirected.
+\f
+redir_xrds
+Status: 302 Found
+Content-Type: application/xrds+xml
+Location: URL_BASE/xrds
+
+<XRDS Content>
+\f
+redir_xrds_html
+Status: 302 Found
+Content-Type: text/plain
+Location: URL_BASE/xrds_html
+
+You are presently being redirected.
+\f
+redir_redir_equiv
+Status: 302 Found
+Content-Type: text/plain
+Location: URL_BASE/redir_equiv
+
+You are presently being redirected.
+\f
+lowercase_header
+Status: 200 OK
+Content-Type: text/html
+x-xrds-location: URL_BASE/xrds
+
+<html>
+<head>
+<title>Joe Schmoe's Homepage</title>
+</head>
+<body>
+<h1>Joe Schmoe's Homepage</h1>
+<p>Blah blah blah blah blah blah blah</p>
+</body>
+\f
+404_server_response
+Status: 404 Not Found
+
+EEk!
+\f
+500_server_response
+Status: 500 Server error
+
+EEk!
+\f
+201_server_response
+Status: 201 Created
+
+EEk!
+\f
+404_with_header
+Status: 404 Not Found
+YADIS_HEADER: URL_BASE/xrds
+
+EEk!
+\f
+404_with_meta
+Status: 404 Not Found
+Content-Type: text/html
+
+<html>
+<head>
+<meta http-equiv="YADIS_HEADER" content="URL_BASE/xrds">
+<title>Joe Schmoe's Homepage</title>
+</head>
+<body>
+<h1>Joe Schmoe's Homepage</h1>
+<p>Blah blah blah blah blah blah blah</p>
+</body>
+</html>
diff --git a/vendor/gems/ruby-openid-2.1.4/test/data/test1-parsehtml.txt b/vendor/gems/ruby-openid-2.1.4/test/data/test1-parsehtml.txt
new file mode 100644 (file)
index 0000000..8ed59d3
--- /dev/null
@@ -0,0 +1,152 @@
+found
+<!-- minimal well-formed success case -->
+<html><head><meta http-equiv="X-XRDS-Location" content="found"></head></html>
+\f
+found
+<!-- minimal well-formed success case, xhtml closing, whitespace -->
+<html><head><meta http-equiv="X-XRDS-Location" content="found" /></head></html>
+\f
+found
+<!-- minimal well-formed success case, xhtml closing, no whitespace -->
+<html><head><meta http-equiv="X-XRDS-Location" content="found"/></head></html>
+\f
+found
+<!-- minimal success case -->
+<html><head><meta http-equiv="X-XRDS-Location" content="found">
+\f
+found
+<!-- ignore bogus top-level tags -->
+</porky><html><head><meta http-equiv="X-XRDS-Location" content="found">
+\f
+found
+<!-- Case folding for header name -->
+<html><head><meta http-equiv="x-xrds-location" content="found">
+\f
+found
+<!-- missing <html> tag -->
+<head><meta http-equiv="X-XRDS-Location" content="found">
+\f
+found
+<!-- javascript in head -->
+<html><head><script type="text/javascript">document.write("<body>");</script><META http-equiv="X-XRDS-Location" content="found">
+\f
+None
+<!-- no close script tag in head -->
+<html><head><script type="text/javascript">document.write("<body>");<META http-equiv="X-XRDS-Location" content="found">
+\f
+found
+<!-- case folding for tag names -->
+<html><head><META http-equiv="X-XRDS-Location" content="found">
+\f
+found
+<!-- Stop after first one found -->
+<html><head>
+<meta http-equiv="x-xrds-location" content="found">
+<meta http-equiv="x-xrds-location" content="not-found">
+\f
+&
+<!-- standard entity -->
+<head><meta http-equiv="X-XRDS-Location" content="&amp;">
+\f
+found
+<!-- hex entity -->
+<html>
+  <head>
+    <meta http-equiv="X-XRDS-Location" content="&#x66;ound">
+  </head>
+</html>
+\f
+found
+<!-- decimal entity -->
+<html>
+  <head>
+    <meta http-equiv="X-XRDS-Location" content="&#102;ound">
+  </head>
+</html>
+\f
+/
+<!-- hex entity -->
+<html>
+  <head>
+    <meta http-equiv="X-XRDS-Location" content="&#x2f;">
+  </head>
+</html>
+\f
+
+<!-- empty string -->
+<html><head><meta http-equiv="X-XRDS-Location" content="">
+\f
+EOF
+<!-- No markup, except this comment -->
+\f
+None
+<!-- No meta, just standard HTML -->
+<html>
+  <head>
+    <title>A boring document</title>
+  </head>
+  <body>
+    <h1>A boring document</h1>
+    <p>There's really nothing interesting about this</p>
+  </body>
+</html>
+\f
+EOF
+<!-- No <html> or <head> -->
+<meta http-equiv="X-XRDS-Location" content="found">
+\f
+EOF
+<!-- No <head> tag -->
+<html><meta http-equiv="X-XRDS-Location" content="found">
+\f
+None
+<!-- No <html> or <head> and a <body> -->
+<body><meta http-equiv="X-XRDS-Location" content="found">
+\f
+None
+<!-- <head> and <html> reversed -->
+<head><html><meta http-equiv="X-XRDS-Location" content="found">
+\f
+None
+<!-- <meta> is inside of <body> -->
+<html><head><body><meta http-equiv="X-XRDS-Location" content="found">
+\f
+None
+<!-- <meta> is inside comment -->
+<html>
+  <head>
+    <!--<meta http-equiv="X-XRDS-Location" content="found">-->
+  </head>
+</html>
+\f
+None
+<!-- <meta> is inside of <body> -->
+<html>
+  <head>
+    <title>Someone's blog</title>
+  </head>
+  <body>
+    <h1>My blog</h1>
+    <p>This is my blog</p>
+    <h2>Comments</h2>
+    <p><meta http-equiv="X-XRDS-Location" content="found"></p>
+  </body>
+</html>
+\f
+None
+<!-- short head tag -->
+<html><head/>
+<meta http-equiv="X-XRDS-Location" content="found">
+\f
+None
+<!-- <body> comes first -->
+<body><html><head>
+<meta http-equiv="X-XRDS-Location" content="found">
+\f
+None
+<!-- </body> comes first -->
+</body><html><head>
+<meta http-equiv="X-XRDS-Location" content="found">
+\f
+None
+<!bad processing instruction!>
diff --git a/vendor/gems/ruby-openid-2.1.4/test/data/test_discover/malformed_meta_tag.html b/vendor/gems/ruby-openid-2.1.4/test/data/test_discover/malformed_meta_tag.html
new file mode 100644 (file)
index 0000000..80dd3b4
--- /dev/null
@@ -0,0 +1,19 @@
+<html>
+<head>
+<title />
+
+<link rel="openid.server"
+       href="http://www.myopenid.com/server" />
+<link rel="openid.delegate"
+       href="http://user.myopenid.com/" />
+<link rel="openid2.local_id"
+       href="http://user.myopenid.com/" />
+<link rel="openid2.provider"
+       href="http://www.myopenid.com/server" />
+<meta http-equiv="X-XRDS-Location"
+       http://www.myopenid.com/xrds?username=user.myopenid.com" />
+
+</head>
+<body>
+</body>
+</html>
diff --git a/vendor/gems/ruby-openid-2.1.4/test/data/test_discover/openid.html b/vendor/gems/ruby-openid-2.1.4/test/data/test_discover/openid.html
new file mode 100644 (file)
index 0000000..1a57d44
--- /dev/null
@@ -0,0 +1,11 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html>
+  <head>
+    <title>Identity Page for Smoker</title>
+    <link rel="openid.server" href="http://www.myopenid.com/server" />
+    <link rel="openid.delegate" href="http://smoker.myopenid.com/" />
+  </head>
+  <body>
+    <p>foo</p>
+  </body>
+</html>
diff --git a/vendor/gems/ruby-openid-2.1.4/test/data/test_discover/openid2.html b/vendor/gems/ruby-openid-2.1.4/test/data/test_discover/openid2.html
new file mode 100644 (file)
index 0000000..a74c042
--- /dev/null
@@ -0,0 +1,11 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html>
+  <head>
+    <title>Identity Page for Smoker</title>
+    <link rel="openid2.provider" href="http://www.myopenid.com/server" />
+    <link rel="openid2.local_id" href="http://smoker.myopenid.com/" />
+  </head>
+  <body>
+    <p>foo</p>
+  </body>
+</html>
diff --git a/vendor/gems/ruby-openid-2.1.4/test/data/test_discover/openid2_xrds.xml b/vendor/gems/ruby-openid-2.1.4/test/data/test_discover/openid2_xrds.xml
new file mode 100644 (file)
index 0000000..8091ab9
--- /dev/null
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<xrds:XRDS xmlns:xrds="xri://$xrds"
+           xmlns="xri://$xrd*($v*2.0)"
+           >
+  <XRD>
+    <Service priority="10">
+      <Type>http://specs.openid.net/auth/2.0/signon</Type>
+      <URI>http://www.myopenid.com/server</URI>
+      <LocalID>http://smoker.myopenid.com/</LocalID>
+    </Service>
+  </XRD>
+</xrds:XRDS>
diff --git a/vendor/gems/ruby-openid-2.1.4/test/data/test_discover/openid2_xrds_no_local_id.xml b/vendor/gems/ruby-openid-2.1.4/test/data/test_discover/openid2_xrds_no_local_id.xml
new file mode 100644 (file)
index 0000000..e6a0eb9
--- /dev/null
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<xrds:XRDS xmlns:xrds="xri://$xrds"
+           xmlns="xri://$xrd*($v*2.0)"
+           >
+  <XRD>
+    <Service priority="10">
+      <Type>http://specs.openid.net/auth/2.0/signon</Type>
+      <URI>http://www.myopenid.com/server</URI>
+    </Service>
+  </XRD>
+</xrds:XRDS>
diff --git a/vendor/gems/ruby-openid-2.1.4/test/data/test_discover/openid_1_and_2.html b/vendor/gems/ruby-openid-2.1.4/test/data/test_discover/openid_1_and_2.html
new file mode 100644 (file)
index 0000000..5e58128
--- /dev/null
@@ -0,0 +1,11 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html>
+  <head>
+    <title>Identity Page for Smoker</title>
+    <link rel="openid2.provider openid.server" href="http://www.myopenid.com/server" />
+    <link rel="openid2.local_id openid.delegate" href="http://smoker.myopenid.com/" />
+  </head>
+  <body>
+    <p>foo</p>
+  </body>
+</html>
diff --git a/vendor/gems/ruby-openid-2.1.4/test/data/test_discover/openid_1_and_2_xrds.xml b/vendor/gems/ruby-openid-2.1.4/test/data/test_discover/openid_1_and_2_xrds.xml
new file mode 100644 (file)
index 0000000..6d85d57
--- /dev/null
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<xrds:XRDS xmlns:xrds="xri://$xrds"
+           xmlns="xri://$xrd*($v*2.0)"
+           xmlns:openid="http://openid.net/xmlns/1.0"
+           >
+  <XRD>
+
+    <Service priority="10">
+      <Type>http://specs.openid.net/auth/2.0/signon</Type>
+      <Type>http://openid.net/signon/1.1</Type>
+      <URI>http://www.myopenid.com/server</URI>
+      <LocalID>http://smoker.myopenid.com/</LocalID>
+      <openid:Delegate>http://smoker.myopenid.com/</openid:Delegate>
+    </Service>
+  </XRD>
+</xrds:XRDS>
diff --git a/vendor/gems/ruby-openid-2.1.4/test/data/test_discover/openid_1_and_2_xrds_bad_delegate.xml b/vendor/gems/ruby-openid-2.1.4/test/data/test_discover/openid_1_and_2_xrds_bad_delegate.xml
new file mode 100644 (file)
index 0000000..db7282e
--- /dev/null
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<xrds:XRDS xmlns:xrds="xri://$xrds"
+           xmlns="xri://$xrd*($v*2.0)"
+           xmlns:openid="http://openid.net/xmlns/1.0"
+           >
+  <XRD>
+
+    <Service priority="10">
+      <Type>http://specs.openid.net/auth/2.0/signon</Type>
+      <Type>http://openid.net/signon/1.0</Type>
+      <Type>http://openid.net/signon/1.1</Type>
+      <URI>http://www.myopenid.com/server</URI>
+      <LocalID>http://smoker.myopenid.com/</LocalID>
+      <openid:Delegate>http://localid.mismatch.invalid/</openid:Delegate>
+    </Service>
+  </XRD>
+</xrds:XRDS>
diff --git a/vendor/gems/ruby-openid-2.1.4/test/data/test_discover/openid_and_yadis.html b/vendor/gems/ruby-openid-2.1.4/test/data/test_discover/openid_and_yadis.html
new file mode 100644 (file)
index 0000000..3befa6f
--- /dev/null
@@ -0,0 +1,12 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html>
+  <head>
+    <title>Identity Page for Smoker</title>
+    <meta http-equiv="X-XRDS-Location" content="http://someuser.unittest/xrds" />
+    <link rel="openid.server" href="http://www.myopenid.com/server" />
+    <link rel="openid.delegate" href="http://smoker.myopenid.com/" />
+  </head>
+  <body>
+    <p>foo</p>
+  </body>
+</html>
diff --git a/vendor/gems/ruby-openid-2.1.4/test/data/test_discover/openid_no_delegate.html b/vendor/gems/ruby-openid-2.1.4/test/data/test_discover/openid_no_delegate.html
new file mode 100644 (file)
index 0000000..f5180b3
--- /dev/null
@@ -0,0 +1,10 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html>
+  <head>
+    <title>Identity Page for Smoker</title>
+    <link rel="openid.server" href="http://www.myopenid.com/server" />
+  </head>
+  <body>
+    <p>foo</p>
+  </body>
+</html>
diff --git a/vendor/gems/ruby-openid-2.1.4/test/data/test_discover/yadis_0entries.xml b/vendor/gems/ruby-openid-2.1.4/test/data/test_discover/yadis_0entries.xml
new file mode 100644 (file)
index 0000000..f161a0b
--- /dev/null
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<xrds:XRDS xmlns:xrds="xri://$xrds"
+           xmlns="xri://$xrd*($v*2.0)"
+           xmlns:openid="http://openid.net/xmlns/1.0"
+           >
+  <XRD>
+    <Service >
+      <Type>http://is-not-openid.unittest/</Type>
+      <URI>http://noffing.unittest./</URI>
+    </Service>
+  </XRD>
+</xrds:XRDS>
diff --git a/vendor/gems/ruby-openid-2.1.4/test/data/test_discover/yadis_2_bad_local_id.xml b/vendor/gems/ruby-openid-2.1.4/test/data/test_discover/yadis_2_bad_local_id.xml
new file mode 100644 (file)
index 0000000..68c2ce1
--- /dev/null
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<xrds:XRDS xmlns:xrds="xri://$xrds"
+           xmlns="xri://$xrd*($v*2.0)"
+           xmlns:openid="http://openid.net/xmlns/1.0"
+           >
+  <XRD>
+
+    <Service priority="10">
+      <Type>http://specs.openid.net/auth/2.0/signon</Type>
+      <URI>http://www.myopenid.com/server</URI>
+      <LocalID>http://smoker.myopenid.com/</LocalID>
+      <LocalID>http://localid.mismatch.invalid/</LocalID>
+    </Service>
+  </XRD>
+</xrds:XRDS>
diff --git a/vendor/gems/ruby-openid-2.1.4/test/data/test_discover/yadis_2entries_delegate.xml b/vendor/gems/ruby-openid-2.1.4/test/data/test_discover/yadis_2entries_delegate.xml
new file mode 100644 (file)
index 0000000..372955b
--- /dev/null
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<xrds:XRDS xmlns:xrds="xri://$xrds"
+           xmlns="xri://$xrd*($v*2.0)"
+           xmlns:openid="http://openid.net/xmlns/1.0"
+           >
+  <XRD>
+    <CanonicalID>=!1000</CanonicalID>
+
+    <Service priority="10">
+      <Type>http://openid.net/signon/1.0</Type>
+      <URI>http://www.myopenid.com/server</URI>
+      <openid:Delegate>http://smoker.myopenid.com/</openid:Delegate>
+    </Service>
+
+    <Service priority="20">
+      <Type>http://openid.net/signon/1.0</Type>
+      <URI>http://www.livejournal.com/openid/server.bml</URI>
+      <openid:Delegate>http://frank.livejournal.com/</openid:Delegate>
+    </Service>
+
+  </XRD>
+</xrds:XRDS>
diff --git a/vendor/gems/ruby-openid-2.1.4/test/data/test_discover/yadis_2entries_idp.xml b/vendor/gems/ruby-openid-2.1.4/test/data/test_discover/yadis_2entries_idp.xml
new file mode 100644 (file)
index 0000000..9a07b3d
--- /dev/null
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<xrds:XRDS xmlns:xrds="xri://$xrds"
+           xmlns="xri://$xrd*($v*2.0)"
+           xmlns:openid="http://openid.net/xmlns/1.0"
+           >
+  <XRD>
+    <CanonicalID>=!1000</CanonicalID>
+
+    <Service priority="10">
+      <Type>http://specs.openid.net/auth/2.0/signon</Type>
+      <URI>http://www.myopenid.com/server</URI>
+      <openid:LocalID>http://smoker.myopenid.com/</openid:LocalID>
+    </Service>
+
+    <Service priority="20">
+      <Type>http://specs.openid.net/auth/2.0/server</Type>
+      <URI>http://www.livejournal.com/openid/server.bml</URI>
+    </Service>
+
+  </XRD>
+</xrds:XRDS>
diff --git a/vendor/gems/ruby-openid-2.1.4/test/data/test_discover/yadis_another_delegate.xml b/vendor/gems/ruby-openid-2.1.4/test/data/test_discover/yadis_another_delegate.xml
new file mode 100644 (file)
index 0000000..2f3b9af
--- /dev/null
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<xrds:XRDS xmlns:xrds="xri://$xrds"
+           xmlns="xri://$xrd*($v*2.0)"
+           xmlns:openid="http://openid.net/xmlns/1.0"
+           >
+  <XRD>
+
+    <Service priority="10">
+      <Type>http://openid.net/signon/1.0</Type>
+      <URI>http://vroom.unittest/server</URI>
+      <openid:Delegate>http://smoker.myopenid.com/</openid:Delegate>
+    </Service>
+  </XRD>
+</xrds:XRDS>
diff --git a/vendor/gems/ruby-openid-2.1.4/test/data/test_discover/yadis_idp.xml b/vendor/gems/ruby-openid-2.1.4/test/data/test_discover/yadis_idp.xml
new file mode 100644 (file)
index 0000000..f570d04
--- /dev/null
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<xrds:XRDS xmlns:xrds="xri://$xrds"
+           xmlns="xri://$xrd*($v*2.0)"
+           xmlns:openid="http://openid.net/xmlns/1.0"
+           >
+  <XRD>
+    <Service priority="10">
+      <Type>http://specs.openid.net/auth/2.0/server</Type>
+      <URI>http://www.myopenid.com/server</URI>
+    </Service>
+  </XRD>
+</xrds:XRDS>
diff --git a/vendor/gems/ruby-openid-2.1.4/test/data/test_discover/yadis_idp_delegate.xml b/vendor/gems/ruby-openid-2.1.4/test/data/test_discover/yadis_idp_delegate.xml
new file mode 100644 (file)
index 0000000..5410600
--- /dev/null
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<xrds:XRDS xmlns:xrds="xri://$xrds"
+           xmlns="xri://$xrd*($v*2.0)"
+           xmlns:openid="http://openid.net/xmlns/1.0"
+           >
+  <XRD>
+    <Service>
+      <Type>http://specs.openid.net/auth/2.0/server</Type>
+      <URI>http://www.myopenid.com/server</URI>
+      <openid:Delegate>http://smoker.myopenid.com/</openid:Delegate>
+    </Service>
+  </XRD>
+</xrds:XRDS>
diff --git a/vendor/gems/ruby-openid-2.1.4/test/data/test_discover/yadis_no_delegate.xml b/vendor/gems/ruby-openid-2.1.4/test/data/test_discover/yadis_no_delegate.xml
new file mode 100644 (file)
index 0000000..fbd6734
--- /dev/null
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<xrds:XRDS xmlns:xrds="xri://$xrds"
+           xmlns="xri://$xrd*($v*2.0)"
+           >
+  <XRD>
+    <Service priority="10">
+      <Type>http://openid.net/signon/1.0</Type>
+      <URI>http://www.myopenid.com/server</URI>
+    </Service>
+  </XRD>
+</xrds:XRDS>
diff --git a/vendor/gems/ruby-openid-2.1.4/test/data/test_xrds/=j3h.2007.11.14.xrds b/vendor/gems/ruby-openid-2.1.4/test/data/test_xrds/=j3h.2007.11.14.xrds
new file mode 100644 (file)
index 0000000..f0246e2
--- /dev/null
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<XRDS ref="xri://=j3h" xmlns="xri://$xrds">
+ <XRD xmlns="xri://$xrd*($v*2.0)">
+  <Query>*j3h</Query>
+  <Status code="100"/>
+  <Expires>2007-11-15T01:35:07.000Z</Expires>
+  <ProviderID>xri://=</ProviderID>
+  <LocalID priority="10">!378C.2F61.25D6.F7EB</LocalID>
+  <CanonicalID priority="10">=!378C.2F61.25D6.F7EB</CanonicalID>
+  <Service priority="10">
+   <Type select="true">http://openid.net/signon/1.0</Type>
+   <ProviderID/>
+   <URI append="qxri" priority="2">http://2idi.com/openid/</URI>
+   <URI append="qxri" priority="1">https://2idi.com/openid/</URI>
+  </Service>
+  <Service priority="10">
+   <Type select="true">xri://+i-service*(+contact)*($v*1.0)</Type>
+   <Type match="default"/>
+   <ProviderID/>
+   <Path select="true">(+contact)</Path>
+   <Path match="null"/>
+   <URI append="qxri" priority="1">http://2idi.com/contact/</URI>
+  </Service>
+ </XRD>
+</XRDS>
diff --git a/vendor/gems/ruby-openid-2.1.4/test/data/test_xrds/README b/vendor/gems/ruby-openid-2.1.4/test/data/test_xrds/README
new file mode 100644 (file)
index 0000000..3739cf1
--- /dev/null
@@ -0,0 +1,12 @@
+delegated-20060809.xrds    - results from proxy.xri.net, determined by 
+                             Drummond and Kevin to be incorrect.
+delegated-20060809-r1.xrds - Drummond's 1st correction
+delegated-20060809-r2.xrds - Drummond's 2nd correction
+
+spoofs: keturn's (=!E4)'s attempts to log in with Drummond's i-number (=!D2)
+spoof1.xrds
+spoof2.xrds
+spoof3.xrds - attempt to steal @!C0!D2 by having "at least one" CanonicalID
+    match the $res service ProviderID.
+
+ref.xrds - resolving @ootao*test.ref, which refers to a neustar XRI.
diff --git a/vendor/gems/ruby-openid-2.1.4/test/data/test_xrds/delegated-20060809-r1.xrds b/vendor/gems/ruby-openid-2.1.4/test/data/test_xrds/delegated-20060809-r1.xrds
new file mode 100644 (file)
index 0000000..f994b14
--- /dev/null
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<XRDS ref="xri://@ootao*test1" xmlns="xri://$xrds">
+ <XRD xmlns="xri://$xrd*($v*2.0)">
+  <Query>*ootao</Query>
+  <Status code="100"/>
+  <Expires>2006-08-09T22:07:13.000Z</Expires>
+  <ProviderID>xri://@</ProviderID>
+  <LocalID priority="10">!5BAD.2AA.3C72.AF46</LocalID>
+  <CanonicalID priority="10">@!5BAD.2AA.3C72.AF46</CanonicalID>
+  <Service priority="10">
+   <Type>xri://$res*auth*($v*2.0)</Type>
+   <ProviderID>xri://!!1003</ProviderID>
+   <MediaType>application/xrds+xml;trust=none</MediaType>
+   <URI priority="10">http://resolve.ezibroker.net/resolve/@ootao/</URI>
+  </Service>
+  <Service priority="10">
+   <Type select="true">http://openid.net/signon/1.0</Type>
+   <ProviderID/>
+   <URI append="qxri" priority="1">https://linksafe.ezibroker.net/server/</URI>
+  </Service>
+ </XRD>
+ <XRD xmlns="xri://$xrd*($v*2.0)">
+  <Query>*test1</Query>
+  <Status code="100">SUCCESS</Status>
+  <ProviderID>xri://!!1003</ProviderID>
+  <LocalID>!0000.0000.3B9A.CA01</LocalID>
+  <CanonicalID>@!5BAD.2AA.3C72.AF46!0000.0000.3B9A.CA01</CanonicalID>
+  <Service>
+   <Type select="true">http://openid.net/signon/1.0</Type>
+   <ProviderID/>
+   <URI append="qxri" priority="1">https://linksafe.ezibroker.net/server/</URI>
+  </Service>
+ </XRD>
+</XRDS>
diff --git a/vendor/gems/ruby-openid-2.1.4/test/data/test_xrds/delegated-20060809-r2.xrds b/vendor/gems/ruby-openid-2.1.4/test/data/test_xrds/delegated-20060809-r2.xrds
new file mode 100644 (file)
index 0000000..68c08dc
--- /dev/null
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<XRDS ref="xri://@ootao*test1" xmlns="xri://$xrds">
+ <XRD xmlns="xri://$xrd*($v*2.0)">
+  <Query>*ootao</Query>
+  <Status code="100"/>
+  <Expires>2006-08-09T22:07:13.000Z</Expires>
+  <ProviderID>xri://@</ProviderID>
+  <LocalID priority="10">!5BAD.2AA.3C72.AF46</LocalID>
+  <CanonicalID priority="10">@!5BAD.2AA.3C72.AF46</CanonicalID>
+  <Service priority="10">
+   <Type>xri://$res*auth*($v*2.0)</Type>
+   <ProviderID>xri://@!5BAD.2AA.3C72.AF46</ProviderID>
+   <MediaType>application/xrds+xml;trust=none</MediaType>
+   <URI priority="10">http://resolve.ezibroker.net/resolve/@ootao/</URI>
+  </Service>
+  <Service priority="10">
+   <Type select="true">http://openid.net/signon/1.0</Type>
+   <ProviderID/>
+   <URI append="qxri" priority="1">https://linksafe.ezibroker.net/server/</URI>
+  </Service>
+ </XRD>
+ <XRD xmlns="xri://$xrd*($v*2.0)">
+  <Query>*test1</Query>
+  <Status code="100">SUCCESS</Status>
+  <ProviderID>xri://@!5BAD.2AA.3C72.AF46</ProviderID>
+  <LocalID>!0000.0000.3B9A.CA01</LocalID>
+  <CanonicalID>@!5BAD.2AA.3C72.AF46!0000.0000.3B9A.CA01</CanonicalID>
+  <Service>
+   <Type select="true">http://openid.net/signon/1.0</Type>
+   <ProviderID/>
+   <URI append="qxri" priority="1">https://linksafe.ezibroker.net/server/</URI>
+  </Service>
+ </XRD>
+</XRDS>
diff --git a/vendor/gems/ruby-openid-2.1.4/test/data/test_xrds/delegated-20060809.xrds b/vendor/gems/ruby-openid-2.1.4/test/data/test_xrds/delegated-20060809.xrds
new file mode 100644 (file)
index 0000000..073ee68
--- /dev/null
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<XRDS ref="xri://@ootao*test1" xmlns="xri://$xrds">
+ <XRD xmlns="xri://$xrd*($v*2.0)">
+  <Query>*ootao</Query>
+  <Status code="100"/>
+  <Expires>2006-08-09T22:07:13.000Z</Expires>
+  <ProviderID>xri://@</ProviderID>
+  <LocalID priority="10">!5BAD.2AA.3C72.AF46</LocalID>
+  <CanonicalID priority="10">@!5BAD.2AA.3C72.AF46</CanonicalID>
+  <Service priority="10">
+   <Type>xri://$res*auth*($v*2.0)</Type>
+   <ProviderID/>
+   <MediaType>application/xrds+xml;trust=none</MediaType>
+   <URI priority="10">http://resolve.ezibroker.net/resolve/@ootao/</URI>
+  </Service>
+  <Service priority="10">
+   <Type select="true">http://openid.net/signon/1.0</Type>
+   <ProviderID/>
+   <URI append="qxri" priority="1">https://linksafe.ezibroker.net/server/</URI>
+  </Service>
+ </XRD>
+ <XRD xmlns="xri://$xrd*($v*2.0)">
+  <Query>*test1</Query>
+  <Status code="100">SUCCESS</Status>
+  <ProviderID>xri://!!1003</ProviderID>
+  <LocalID>!0000.0000.3B9A.CA01</LocalID>
+  <CanonicalID>@!5BAD.2AA.3C72.AF46!0000.0000.3B9A.CA01</CanonicalID>
+  <Service>
+   <Type select="true">http://openid.net/signon/1.0</Type>
+   <ProviderID/>
+   <URI append="qxri" priority="1">https://linksafe.ezibroker.net/server/</URI>
+  </Service>
+ </XRD>
+</XRDS>
diff --git a/vendor/gems/ruby-openid-2.1.4/test/data/test_xrds/no-xrd.xml b/vendor/gems/ruby-openid-2.1.4/test/data/test_xrds/no-xrd.xml
new file mode 100644 (file)
index 0000000..ca66f73
--- /dev/null
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<xrds:XRDS
+    xmlns:xrds="xri://$xrds"
+    xmlns:openid="http://openid.net/xmlns/1.0"
+    xmlns:typekey="http://typekey.com/xmlns/1.0"
+    xmlns="xri://$xrd*($v*2.0)">
+</xrds:XRDS>
diff --git a/vendor/gems/ruby-openid-2.1.4/test/data/test_xrds/not-xrds.xml b/vendor/gems/ruby-openid-2.1.4/test/data/test_xrds/not-xrds.xml
new file mode 100644 (file)
index 0000000..7f5bfd5
--- /dev/null
@@ -0,0 +1,2 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<x></x>
diff --git a/vendor/gems/ruby-openid-2.1.4/test/data/test_xrds/prefixsometimes.xrds b/vendor/gems/ruby-openid-2.1.4/test/data/test_xrds/prefixsometimes.xrds
new file mode 100644 (file)
index 0000000..5522a6e
--- /dev/null
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<XRDS ref="xri://@ootao*test1" xmlns="xri://$xrds">
+ <XRD xmlns="xri://$xrd*($v*2.0)">
+  <Query>*ootao</Query>
+  <Status code="100"/>
+  <Expires>2006-08-09T22:07:13.000Z</Expires>
+  <ProviderID>xri://@</ProviderID>
+  <LocalID priority="10">!5BAD.2AA.3C72.AF46</LocalID>
+  <CanonicalID priority="10">@!5BAD.2AA.3C72.AF46</CanonicalID>
+  <Service priority="10">
+   <Type>xri://$res*auth*($v*2.0)</Type>
+   <ProviderID>xri://@!5BAD.2AA.3C72.AF46</ProviderID>
+   <MediaType>application/xrds+xml;trust=none</MediaType>
+   <URI priority="10">http://resolve.ezibroker.net/resolve/@ootao/</URI>
+  </Service>
+  <Service priority="10">
+   <Type select="true">http://openid.net/signon/1.0</Type>
+   <ProviderID/>
+   <URI append="qxri" priority="1">https://linksafe.ezibroker.net/server/</URI>
+  </Service>
+ </XRD>
+ <XRD xmlns="xri://$xrd*($v*2.0)">
+  <Query>*test1</Query>
+  <Status code="100">SUCCESS</Status>
+  <ProviderID>xri://@!5BAD.2AA.3C72.AF46</ProviderID>
+  <LocalID>!0000.0000.3B9A.CA01</LocalID>
+  <CanonicalID>xri://@!5BAD.2AA.3C72.AF46!0000.0000.3B9A.CA01</CanonicalID>
+  <Service>
+   <Type select="true">http://openid.net/signon/1.0</Type>
+   <ProviderID/>
+   <URI append="qxri" priority="1">https://linksafe.ezibroker.net/server/</URI>
+  </Service>
+ </XRD>
+</XRDS>
diff --git a/vendor/gems/ruby-openid-2.1.4/test/data/test_xrds/ref.xrds b/vendor/gems/ruby-openid-2.1.4/test/data/test_xrds/ref.xrds
new file mode 100644 (file)
index 0000000..69cf683
--- /dev/null
@@ -0,0 +1,109 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<XRDS ref="xri://@ootao*test.ref" xmlns="xri://$xrds">
+ <XRD xmlns="xri://$xrd*($v*2.0)">
+  <Query>*ootao</Query>
+  <Status code="100"/>
+  <Expires>2006-08-15T18:56:09.000Z</Expires>
+  <ProviderID>xri://@</ProviderID>
+  <LocalID priority="10">!5BAD.2AA.3C72.AF46</LocalID>
+  <CanonicalID priority="10">@!5BAD.2AA.3C72.AF46</CanonicalID>
+  <Service priority="10">
+   <Type>xri://$res*auth*($v*2.0)</Type>
+   <ProviderID/>
+   <MediaType>application/xrds+xml;trust=none</MediaType>
+   <URI priority="10">http://resolve.ezibroker.net/resolve/@ootao/</URI>
+  </Service>
+  <Service priority="10">
+   <Type select="true">http://openid.net/signon/1.0</Type>
+   <ProviderID/>
+   <URI append="qxri" priority="1">https://linksafe.ezibroker.net/server/</URI>
+  </Service>
+ </XRD>
+ <XRD xmlns="xri://$xrd*($v*2.0)">
+  <Query>*test.ref</Query>
+  <Status code="100">SUCCESS</Status>
+  <ProviderID>xri://!!1003</ProviderID>
+  <LocalID>!0000.0000.3B9A.CA03</LocalID>
+  <CanonicalID>@!5BAD.2AA.3C72.AF46!0000.0000.3B9A.CA03</CanonicalID>
+  <Ref>@!BAE.A650.823B.2475</Ref>
+  <Service>
+   <Type select="true">http://openid.net/signon/1.0</Type>
+   <ProviderID/>
+   <URI append="qxri" priority="1">https://linksafe.ezibroker.net/server/</URI>
+  </Service>
+ </XRD>
+ <XRDS ref="xri://@!BAE.A650.823B.2475" xmlns="xri://$xrds">
+  <XRD xmlns="xri://$xrd*($v*2.0)">
+   <Query>!BAE.A650.823B.2475</Query>
+   <Status code="100"/>
+   <Expires>2006-08-15T18:56:10.000Z</Expires>
+   <ProviderID>xri://@</ProviderID>
+   <LocalID priority="10">!BAE.A650.823B.2475</LocalID>
+   <CanonicalID priority="10">@!BAE.A650.823B.2475</CanonicalID>
+   <Service priority="10">
+    <Type select="true">(+wdnc)</Type>
+    <ProviderID/>
+    <Path select="true">(+wdnc)</Path>
+    <URI append="none" priority="10">http://www.tcpacompliance.us</URI>
+   </Service>
+   <Service priority="10">
+    <Type match="content" select="true">xri://$res*auth*($v*2.0)</Type>
+    <ProviderID/>
+    <MediaType match="content" select="false">application/xrds+xml;trust=none</MediaType>
+    <URI priority="10">http://dev.dready.org/cgi-bin/xri</URI>
+   </Service>
+   <Service priority="10">
+    <Type match="content" select="true">(+i-name)</Type>
+    <ProviderID/>
+    <Path match="content" select="true">(+i-name)</Path>
+    <URI append="none" priority="10">http://www.inames.net</URI>
+   </Service>
+   <Service priority="10">
+    <Type select="true">xri://+i-service*(+contact)*($v*1.0)</Type>
+    <Type match="default" select="false"/>
+    <ProviderID>xri://!!1001</ProviderID>
+    <Path select="true">(+contact)</Path>
+    <Path match="null" select="false"/>
+    <MediaType select="false">text/html</MediaType>
+    <MediaType match="default" select="false"/>
+    <URI append="none" priority="10">http://www.neustar.biz</URI>
+   </Service>
+  </XRD>
+ </XRDS>
+ <XRD xmlns="xri://$xrd*($v*2.0)">
+  <Query>!BAE.A650.823B.2475</Query>
+  <Status code="100"/>
+  <Expires>2006-08-15T18:56:10.000Z</Expires>
+  <ProviderID>xri://@</ProviderID>
+  <LocalID priority="10">!BAE.A650.823B.2475</LocalID>
+  <CanonicalID priority="10">@!BAE.A650.823B.2475</CanonicalID>
+  <Service priority="10">
+   <Type select="true">(+wdnc)</Type>
+   <ProviderID/>
+   <Path select="true">(+wdnc)</Path>
+   <URI append="none" priority="10">http://www.tcpacompliance.us</URI>
+  </Service>
+  <Service priority="10">
+   <Type match="content" select="true">xri://$res*auth*($v*2.0)</Type>
+   <ProviderID/>
+   <MediaType match="content" select="false">application/xrds+xml;trust=none</MediaType>
+   <URI priority="10">http://dev.dready.org/cgi-bin/xri</URI>
+  </Service>
+  <Service priority="10">
+   <Type match="content" select="true">(+i-name)</Type>
+   <ProviderID/>
+   <Path match="content" select="true">(+i-name)</Path>
+   <URI append="none" priority="10">http://www.inames.net</URI>
+  </Service>
+  <Service priority="10">
+   <Type select="true">xri://+i-service*(+contact)*($v*1.0)</Type>
+   <Type match="default" select="false"/>
+   <ProviderID>xri://!!1001</ProviderID>
+   <Path select="true">(+contact)</Path>
+   <Path match="null" select="false"/>
+   <MediaType select="false">text/html</MediaType>
+   <MediaType match="default" select="false"/>
+   <URI append="none" priority="10">http://www.neustar.biz</URI>
+  </Service>
+ </XRD>
+</XRDS>
\ No newline at end of file
diff --git a/vendor/gems/ruby-openid-2.1.4/test/data/test_xrds/sometimesprefix.xrds b/vendor/gems/ruby-openid-2.1.4/test/data/test_xrds/sometimesprefix.xrds
new file mode 100644 (file)
index 0000000..eff7555
--- /dev/null
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<XRDS ref="xri://@ootao*test1" xmlns="xri://$xrds">
+ <XRD xmlns="xri://$xrd*($v*2.0)">
+  <Query>*ootao</Query>
+  <Status code="100"/>
+  <Expires>2006-08-09T22:07:13.000Z</Expires>
+  <ProviderID>xri://@</ProviderID>
+  <LocalID priority="10">!5BAD.2AA.3C72.AF46</LocalID>
+  <CanonicalID priority="10">xri://@!5BAD.2AA.3C72.AF46</CanonicalID>
+  <Service priority="10">
+   <Type>xri://$res*auth*($v*2.0)</Type>
+   <ProviderID>xri://@!5BAD.2AA.3C72.AF46</ProviderID>
+   <MediaType>application/xrds+xml;trust=none</MediaType>
+   <URI priority="10">http://resolve.ezibroker.net/resolve/@ootao/</URI>
+  </Service>
+  <Service priority="10">
+   <Type select="true">http://openid.net/signon/1.0</Type>
+   <ProviderID/>
+   <URI append="qxri" priority="1">https://linksafe.ezibroker.net/server/</URI>
+  </Service>
+ </XRD>
+ <XRD xmlns="xri://$xrd*($v*2.0)">
+  <Query>*test1</Query>
+  <Status code="100">SUCCESS</Status>
+  <ProviderID>xri://@!5BAD.2AA.3C72.AF46</ProviderID>
+  <LocalID>!0000.0000.3B9A.CA01</LocalID>
+  <CanonicalID>@!5BAD.2AA.3C72.AF46!0000.0000.3B9A.CA01</CanonicalID>
+  <Service>
+   <Type select="true">http://openid.net/signon/1.0</Type>
+   <ProviderID/>
+   <URI append="qxri" priority="1">https://linksafe.ezibroker.net/server/</URI>
+  </Service>
+ </XRD>
+</XRDS>
diff --git a/vendor/gems/ruby-openid-2.1.4/test/data/test_xrds/spoof1.xrds b/vendor/gems/ruby-openid-2.1.4/test/data/test_xrds/spoof1.xrds
new file mode 100644 (file)
index 0000000..8e870c8
--- /dev/null
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<XRDS ref="xri://=keturn*isDrummond" xmlns="xri://$xrds">
+ <XRD xmlns="xri://$xrd*($v*2.0)">
+ <Query>*keturn</Query>
+ <ProviderID>xri://=</ProviderID>
+ <LocalID>!E4</LocalID>
+ <CanonicalID>=!E4</CanonicalID>
+
+ <Service>
+   <Type>xri://$res*auth*($v*2.0)</Type>
+   <URI>http://keturn.example.com/resolve/</URI>
+   <ProviderID>=!E4</ProviderID>
+ </Service>
+ </XRD>
+ <XRD xmlns="xri://$xrd*($v*2.0)">
+  <Query>*isDrummond</Query>
+  <ProviderID>=!E4</ProviderID>
+  <LocalID>!D2</LocalID>
+  <CanonicalID>=!D2</CanonicalID>
+  <Service>
+    <Type>http://openid.net/signon/1.0</Type>
+    <URI>http://keturn.example.com/openid</URI>
+  </Service>
+ </XRD>
+</XRDS>
diff --git a/vendor/gems/ruby-openid-2.1.4/test/data/test_xrds/spoof2.xrds b/vendor/gems/ruby-openid-2.1.4/test/data/test_xrds/spoof2.xrds
new file mode 100644 (file)
index 0000000..7547561
--- /dev/null
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<XRDS ref="xri://=keturn*isDrummond" xmlns="xri://$xrds">
+ <XRD xmlns="xri://$xrd*($v*2.0)">
+ <Query>*keturn</Query>
+ <ProviderID>xri://=</ProviderID>
+ <LocalID>!E4</LocalID>
+ <CanonicalID>=!E4</CanonicalID>
+
+ <Service>
+   <Type>xri://$res*auth*($v*2.0)</Type>
+   <URI>http://keturn.example.com/resolve/</URI>
+   <ProviderID>xri://=</ProviderID>
+ </Service>
+ </XRD>
+ <XRD xmlns="xri://$xrd*($v*2.0)">
+  <Query>*isDrummond</Query>
+  <ProviderID>xri://=</ProviderID>
+  <LocalID>!D2</LocalID>
+  <CanonicalID>=!D2</CanonicalID>
+  <Service>
+    <Type>http://openid.net/signon/1.0</Type>
+    <URI>http://keturn.example.com/openid</URI>
+  </Service>
+ </XRD>
+</XRDS>
diff --git a/vendor/gems/ruby-openid-2.1.4/test/data/test_xrds/spoof3.xrds b/vendor/gems/ruby-openid-2.1.4/test/data/test_xrds/spoof3.xrds
new file mode 100644 (file)
index 0000000..f4c43c9
--- /dev/null
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<XRDS ref="xri://=keturn*isDrummond" xmlns="xri://$xrds">
+ <XRD xmlns="xri://$xrd*($v*2.0)">
+ <Query>*keturn</Query>
+ <ProviderID>xri://@</ProviderID>
+ <LocalID>@E4</LocalID>
+ <CanonicalID>@!E4</CanonicalID>
+
+ <Service>
+   <Type>xri://$res*auth*($v*2.0)</Type>
+   <URI>http://keturn.example.com/resolve/</URI>
+   <ProviderID>@!E4</ProviderID>
+ </Service>
+ </XRD>
+ <XRD xmlns="xri://$xrd*($v*2.0)">
+  <Query>*is</Query>
+  <ProviderID>@!E4</ProviderID>
+  <LocalID>!D2</LocalID>
+  <CanonicalID>=!C0</CanonicalID>
+  <CanonicalID>=!E4!01</CanonicalID>
+  <Service>
+    <Type>xri://$res*auth*($v*2.0)</Type>
+    <URI>http://keturn.example.com/resolve/</URI>
+    <ProviderID>@!C0</ProviderID>
+  </Service>
+ </XRD>
+ <XRD xmlns="xri://$xrd*($v*2.0)">
+  <Query>*drummond</Query>
+  <ProviderID>@!C0</ProviderID>
+  <LocalID>!D2</LocalID>
+  <CanonicalID>@!C0!D2</CanonicalID>
+  <Service>
+    <Type>http://openid.net/signon/1.0</Type>
+    <URI>http://keturn.example.com/openid</URI>
+  </Service>
+ </XRD>
+</XRDS>
diff --git a/vendor/gems/ruby-openid-2.1.4/test/data/test_xrds/status222.xrds b/vendor/gems/ruby-openid-2.1.4/test/data/test_xrds/status222.xrds
new file mode 100644 (file)
index 0000000..84cd5c9
--- /dev/null
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<XRDS ref="xri://=x" xmlns="xri://$xrds">
+ <XRD xmlns="xri://$xrd*($v*2.0)">
+  <Query>*x</Query>
+  <Status code="222">The subsegment does not exist</Status>
+  <Expires>2006-08-18T00:02:35.000Z</Expires>
+  <ProviderID>xri://=</ProviderID>
+ </XRD>
+</XRDS>
\ No newline at end of file
diff --git a/vendor/gems/ruby-openid-2.1.4/test/data/test_xrds/subsegments.xrds b/vendor/gems/ruby-openid-2.1.4/test/data/test_xrds/subsegments.xrds
new file mode 100644 (file)
index 0000000..11d2e91
--- /dev/null
@@ -0,0 +1,58 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<XRDS ref="xri://=nishitani*masaki" xmlns="xri://$xrds">
+ <XRD xmlns="xri://$xrd*($v*2.0)">
+  <Query>*nishitani</Query>
+  <Status code="100"/>
+  <Expires>2007-12-25T11:33:39.000Z</Expires>
+  <ProviderID>xri://=</ProviderID>
+  <LocalID priority="10">!E117.EF2F.454B.C707</LocalID>
+  <CanonicalID priority="10">=!E117.EF2F.454B.C707</CanonicalID>
+  <Service priority="10">
+   <Type select="true">http://openid.net/signon/1.0</Type>
+   <ProviderID>xri://!!1003!103</ProviderID>
+   <URI append="none" priority="1">https://linksafe.ezibroker.net/server/</URI>
+  </Service>
+  <Service priority="10">
+   <Type select="true">xri://$res*auth*($v*2.0)</Type>
+   <ProviderID>xri://!!1003!103</ProviderID>
+   <MediaType>application/xrds+xml;trust=none</MediaType>
+   <URI priority="10">http://resolve.ezibroker.net/resolve/=nishitani/</URI>
+  </Service>
+  <Service priority="1">
+   <Type match="content" select="true">xri://+i-service*(+forwarding)*($v*1.0)</Type>
+   <Type match="null" select="false"/>
+   <ProviderID>xri://!!1003!103</ProviderID>
+   <Path match="content">(+index)</Path>
+   <Path match="default"/>
+   <URI append="qxri" priority="1">http://linksafe-forward.ezibroker.net/forwarding/</URI>
+  </Service>
+ </XRD>
+ <XRD xmlns="xri://$xrd*($v*2.0)">
+  <Query>*masaki</Query>
+  <Status code="100">SUCCESS</Status>
+  <ProviderID>xri://!!1003</ProviderID>
+  <LocalID>!0000.0000.3B9A.CA01</LocalID>
+  <CanonicalID>=!E117.EF2F.454B.C707!0000.0000.3B9A.CA01</CanonicalID>
+  <Service>
+   <Type select="true">http://openid.net/signon/1.0</Type>
+   <ProviderID>xri://!!1003!103</ProviderID>
+   <URI append="none" priority="1">https://linksafe.ezibroker.net/server/</URI>
+  </Service>
+  <Service>
+   <Type select="true">xri://+i-service*(+contact)*($v*1.0)</Type>
+   <Type match="null"/>
+   <ProviderID>xri://!!1003!103</ProviderID>
+   <Path select="true">(+contact)</Path>
+   <Path match="null"/>
+   <URI append="authority" priority="1">http://linksafe-contact.ezibroker.net/contact/</URI>
+  </Service>
+  <Service priority="1">
+   <Type match="content" select="true">xri://+i-service*(+forwarding)*($v*1.0)</Type>
+   <Type match="null" select="false"/>
+   <ProviderID>xri://!!1003!103</ProviderID>
+   <Path match="content">(+index)</Path>
+   <Path match="default"/>
+   <URI append="qxri" priority="1">http://linksafe-forward.ezibroker.net/forwarding/</URI>
+  </Service>
+ </XRD>
+</XRDS>
diff --git a/vendor/gems/ruby-openid-2.1.4/test/data/test_xrds/valid-populated-xrds.xml b/vendor/gems/ruby-openid-2.1.4/test/data/test_xrds/valid-populated-xrds.xml
new file mode 100644 (file)
index 0000000..60e5ca7
--- /dev/null
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<xrds:XRDS
+    xmlns:xrds="xri://$xrds"
+    xmlns:openid="http://openid.net/xmlns/1.0"
+    xmlns:typekey="http://typekey.com/xmlns/1.0"
+    xmlns="xri://$xrd*($v*2.0)">
+  <XRD>
+
+    <Service priority="0">
+      <Type>http://openid.net/signon/1.0</Type>
+      <URI>http://www.myopenid.com/server</URI>
+      <openid:Delegate>http://josh.myopenid.com/</openid:Delegate>
+    </Service>
+
+    <Service priority="20">
+      <Type>http://lid.netmesh.org/sso/2.0b5</Type>
+      <Type>http://lid.netmesh.org/2.0b5</Type>
+      <URI>http://mylid.net/josh</URI>
+    </Service>
+
+    <Service priority="10">
+      <Type>http://openid.net/signon/1.0</Type>
+      <URI>http://www.livejournal.com/openid/server.bml</URI>
+      <openid:Delegate>http://www.livejournal.com/users/nedthealpaca/</openid:Delegate>
+    </Service>
+
+    <Service priority="15">
+      <Type>http://typekey.com/services/1.0</Type>
+      <typekey:MemberName>joshhoyt</typekey:MemberName>
+    </Service>
+
+    <Service priority="5">
+      <Type>http://openid.net/signon/1.0</Type>
+      <URI>http://www.schtuff.com/openid</URI>
+      <openid:Delegate>http://users.schtuff.com/josh</openid:Delegate>
+    </Service>
+
+  </XRD>
+</xrds:XRDS>
diff --git a/vendor/gems/ruby-openid-2.1.4/test/data/trustroot.txt b/vendor/gems/ruby-openid-2.1.4/test/data/trustroot.txt
new file mode 100644 (file)
index 0000000..7368165
--- /dev/null
@@ -0,0 +1,153 @@
+========================================
+Trust root parsing checking
+========================================
+
+----------------------------------------
+23: Does not parse
+----------------------------------------
+baz.org
+*.foo.com
+http://*.schtuff.*/
+ftp://foo.com
+ftp://*.foo.com
+http://*.foo.com:80:90/
+foo.*.com
+http://foo.*.com
+http://www.*
+http://*foo.com/
+http://foo.com/invalid#fragment
+http://..it/
+http://.it/
+http://*:8081/
+http://*:80
+http://localhost:1900foo/
+http://foo.com\/
+http://Ï€.pi.com/
+http://lambda.com/Λ
+
+       
+5
+
+----------------------------------------
+14: Insane
+----------------------------------------
+http:///
+http://*/
+https://*/
+http://*.com
+http://*.com/
+https://*.com/
+http://*.com.au/
+http://*.co.uk/
+http://*.foo.notatld/
+https://*.foo.notatld/
+http://*.museum/
+https://*.museum/
+http://www.schtuffcom/
+http://it/
+
+----------------------------------------
+18: Sane
+----------------------------------------
+http://*.schtuff.com./
+http://*.schtuff.com/
+http://*.foo.schtuff.com/
+http://*.schtuff.com
+http://www.schtuff.com/
+http://www.schtuff.com./
+http://www.schutff.com
+http://*.this.that.schtuff.com/
+http://*.foo.com/path
+http://*.foo.com/path?action=foo2
+http://x.foo.com/path?action=foo2
+http://x.foo.com/path?action=%3D
+http://localhost:8081/
+http://localhost:8082/?action=openid
+https://foo.com/
+http://kink.fm/should/be/sane
+http://beta.lingu.no/
+http://goathack.livejournal.org:8020/openid/login.bml
+
+========================================
+return_to matching
+========================================
+
+----------------------------------------
+46: matches
+----------------------------------------
+http://*/                             http://cnn.com/
+http://*/                             http://livejournal.com/
+http://*/                             http://met.museum/
+http://localhost:8081/x?action=openid http://localhost:8081/x?action=openid
+http://*.foo.com                      http://b.foo.com
+http://*.foo.com                      http://b.foo.com/
+http://*.foo.com/                     http://b.foo.com
+http://b.foo.com                      http://b.foo.com
+http://b.foo.com                      http://b.foo.com/
+http://b.foo.com/                     http://b.foo.com
+http://*.b.foo.com                    http://b.foo.com
+http://*.b.foo.com                    http://b.foo.com/
+http://*.b.foo.com/                   http://b.foo.com
+http://*.b.foo.com                    http://x.b.foo.com
+http://*.b.foo.com                    http://w.x.b.foo.com
+http://*.bar.co.uk                    http://www.bar.co.uk
+http://*.uoregon.edu                  http://x.cs.uoregon.edu
+http://x.com/abc                      http://x.com/abc
+http://x.com/abc                      http://x.com/abc/def
+http://10.0.0.1/abc                   http://10.0.0.1/abc
+http://*.x.com                        http://x.com/gallery
+http://*.x.com                        http://foo.x.com/gallery
+http://foo.x.com                      http://foo.x.com/gallery/xxx
+http://*.x.com/gallery                http://foo.x.com/gallery
+http://localhost:8082/?action=openid  http://localhost:8082/?action=openid
+http://goathack.livejournal.org:8020/ http://goathack.livejournal.org:8020/openid/login.bml
+https://foo.com                       https://foo.com
+http://Foo.com                        http://foo.com
+http://foo.com                        http://Foo.com
+http://foo.com:80/                    http://foo.com/
+http://foo.com/?x=y                   http://foo.com/?x=y&a=b
+http://foo.com/x                      http://foo.com/x?y
+http://mylid.net/j3h.                 http://mylid.net/j3h.?x=y
+http://j3h.us                         http://j3h.us?ride=unicycle
+https://www.filmclans.com:443/mattmartin/FilmClans https://www.filmclans.com/mattmartin/FilmClans/Logon.aspx?nonce=BVjqSOee
+http://foo.com:80                     http://foo.com
+http://foo.com                        http://foo.com:80
+http://foo.com                        http://foo.com/
+http://foo.com/                       http://foo.com
+http://foo.com/                       http://foo.com:80
+http://foo.com:80/                    http://foo.com:80/stuff
+http://foo.com:80/                    http://foo.com/stuff
+http://foo.com/path                   http://foo.com/path/extra
+http://foo.com/path2                  http://foo.com/path2?extra=query
+http://foo.com/path2                  http://foo.com/path2/?extra=query
+http://foo.com/                       HTTP://foo.com/
+
+----------------------------------------
+25: does not match
+----------------------------------------
+http://*/                             ftp://foo.com/
+http://*/                             xxx
+http://foo.com/                       http://oo.com/
+http://*.x.com/abc                    http://foo.x.com
+http://*.x.com/abc                    http://*.x.com
+http://*.com/                         http://*.com/
+http://x.com/abc                      http://x.com/
+http://x.com/abc                      http://x.com/a
+http://x.com/abc                      http://x.com/ab
+http://x.com/abc                      http://x.com/abcd
+http://*.cs.uoregon.edu               http://x.uoregon.edu
+http://*.foo.com                      http://bar.com
+http://*.foo.com                      http://www.bar.com
+http://*.bar.co.uk                    http://xxx.co.uk
+https://foo.com                       http://foo.com
+http://foo.com                        https://foo.com
+http://foo.com:81                     http://foo.com:80
+http://foo.com/?a=b                   http://foo.com/?x=y
+http://foo.com/?a=b                   http://foo.com/?x=y&a=b
+http://foo.com/?a=b                   http://foo.com/
+http://*.oo.com/                      http://foo.com/
+http://foo.com/*                      http://foo.com/anything
+http://foo.com                        http://foo.com:443
+https://foo.com                       https://foo.com:80
+http://foo.com/path/xev               http://foo.com/path?extra=more
diff --git a/vendor/gems/ruby-openid-2.1.4/test/data/urinorm.txt b/vendor/gems/ruby-openid-2.1.4/test/data/urinorm.txt
new file mode 100644 (file)
index 0000000..95262fe
--- /dev/null
@@ -0,0 +1,79 @@
+Already normal form
+http://example.com/
+http://example.com/
+
+Add a trailing slash
+http://example.com
+http://example.com/
+
+Remove an empty port segment
+http://example.com:/
+http://example.com/
+
+Remove a default port segment
+http://example.com:80/
+http://example.com/
+
+Capitalization in host names
+http://wWw.exaMPLE.COm/
+http://www.example.com/
+
+Capitalization in scheme names
+htTP://example.com/
+http://example.com/
+
+Capitalization in percent-escaped reserved characters
+http://example.com/foo%2cbar
+http://example.com/foo%2Cbar
+
+Unescape percent-encoded unreserved characters
+http://example.com/foo%2Dbar%2dbaz
+http://example.com/foo-bar-baz
+
+remove_dot_segments example 1
+http://example.com/a/b/c/./../../g
+http://example.com/a/g
+
+remove_dot_segments example 2
+http://example.com/mid/content=5/../6
+http://example.com/mid/6
+
+remove_dot_segments: single-dot
+http://example.com/a/./b
+http://example.com/a/b
+
+remove_dot_segments: double-dot
+http://example.com/a/../b
+http://example.com/b
+
+remove_dot_segments: leading double-dot
+http://example.com/../b
+http://example.com/b
+
+remove_dot_segments: trailing single-dot
+http://example.com/a/.
+http://example.com/a/
+
+remove_dot_segments: trailing double-dot
+http://example.com/a/..
+http://example.com/
+
+remove_dot_segments: trailing single-dot-slash
+http://example.com/a/./
+http://example.com/a/
+
+remove_dot_segments: trailing double-dot-slash
+http://example.com/a/../
+http://example.com/
+
+Test of all kinds of syntax-based normalization
+hTTPS://a/./b/../b/%63/%7bfoo%7d
+https://a/b/c/%7Bfoo%7D
+
+Unsupported scheme
+ftp://example.com/
+fail
+
+Non-absolute URI
+http:/foo
+fail
\ No newline at end of file
diff --git a/vendor/gems/ruby-openid-2.1.4/test/discoverdata.rb b/vendor/gems/ruby-openid-2.1.4/test/discoverdata.rb
new file mode 100644 (file)
index 0000000..7b21749
--- /dev/null
@@ -0,0 +1,131 @@
+
+require 'uri'
+require 'openid/yadis/constants'
+require 'openid/yadis/discovery'
+require 'openid/extras'
+require 'openid/util'
+
+module OpenID
+
+  module DiscoverData
+
+    include TestDataMixin
+    include Util
+
+    TESTLIST = [
+                # success,  input_name,          id_name,            result_name
+                [true,  "equiv",             "equiv",            "xrds"],
+                [true,  "header",            "header",           "xrds"],
+                [true,  "lowercase_header",  "lowercase_header", "xrds"],
+                [true,  "xrds",              "xrds",             "xrds"],
+                [true,  "xrds_ctparam",      "xrds_ctparam",     "xrds_ctparam"],
+                [true,  "xrds_ctcase",       "xrds_ctcase",      "xrds_ctcase"],
+                [false, "xrds_html",         "xrds_html",        "xrds_html"],
+                [true,  "redir_equiv",       "equiv",            "xrds"],
+                [true,  "redir_header",      "header",           "xrds"],
+                [true,  "redir_xrds",        "xrds",             "xrds"],
+                [false, "redir_xrds_html",   "xrds_html",        "xrds_html"],
+                [true,  "redir_redir_equiv", "equiv",            "xrds"],
+                [false, "404_server_response", nil,             nil],
+                [false, "404_with_header",     nil,             nil],
+                [false, "404_with_meta",       nil,             nil],
+                [false, "201_server_response", nil,             nil],
+                [false, "500_server_response", nil,             nil],
+             ]
+
+    @@example_xrds_file = 'example-xrds.xml'
+    @@default_test_file = 'test1-discover.txt'
+    @@discover_tests = {}
+
+    def readTests(filename)
+      data = read_data_file(filename, false)
+      tests = {}
+      data.split("\f\n", -1).each { |case_|
+        name, content = case_.split("\n", 2)
+        tests[name] = content
+      }
+
+      return tests
+    end
+
+    def getData(filename, name)
+      if !@@discover_tests.member?(filename)
+        @@discover_tests[filename] = readTests(filename)
+      end
+
+      file_tests = @@discover_tests[filename]
+      return file_tests[name]
+    end
+
+    def fillTemplate(test_name, template, base_url, example_xrds)
+      mapping = [
+                 ['URL_BASE/', base_url],
+                 ['<XRDS Content>', example_xrds],
+                 ['YADIS_HEADER', Yadis::YADIS_HEADER_NAME],
+                 ['NAME', test_name],
+                ]
+
+      mapping.each { |k, v|
+        template = template.gsub(/#{k}/, v)
+      }
+
+      return template
+    end
+
+    def generateSample(test_name, base_url,
+                       example_xrds=nil,
+                       filename=@@default_test_file)
+      if example_xrds.nil?
+        example_xrds = read_data_file(@@example_xrds_file, false)
+      end
+
+      begin
+        template = getData(filename, test_name)
+      rescue Errno::ENOENT
+        raise ArgumentError(filename)
+      end
+
+      return fillTemplate(test_name, template, base_url, example_xrds)
+    end
+
+    def generateResult(base_url, input_name, id_name, result_name, success)
+      uri = URI::parse(base_url)
+
+      input_url = (uri + input_name).to_s
+
+      # If the name is None then we expect the protocol to fail, which
+      # we represent by None
+      if id_name.nil?
+        Util.assert(result_name.nil?)
+        return input_url, DiscoveryFailure
+      end
+
+      result = generateSample(result_name, base_url)
+      headers, content = result.split("\n\n", 2)
+      header_lines = headers.split("\n")
+
+      ctype = nil
+      header_lines.each { |header_line|
+        if header_line.starts_with?('Content-Type:')
+          _, ctype = header_line.split(':', 2)
+          ctype = ctype.strip()
+          break
+        else
+          ctype = nil
+        end
+      }
+
+      id_url = (uri + id_name).to_s
+      result = Yadis::DiscoveryResult.new(input_url)
+      result.normalized_uri = id_url
+
+      if success
+        result.xrds_uri = (uri + result_name).to_s
+      end
+
+      result.content_type = ctype
+      result.response_text = content
+      return [input_url, result]
+    end
+  end
+end
diff --git a/vendor/gems/ruby-openid-2.1.4/test/test_accept.rb b/vendor/gems/ruby-openid-2.1.4/test/test_accept.rb
new file mode 100644 (file)
index 0000000..06db85b
--- /dev/null
@@ -0,0 +1,170 @@
+
+require 'test/unit'
+require 'openid/yadis/accept'
+require 'openid/extras'
+require 'openid/util'
+
+module OpenID
+
+  class AcceptTest < Test::Unit::TestCase
+    include TestDataMixin
+
+    def getTestData()
+      # Read the test data off of disk
+      #
+      # () -> [(int, str)]
+      lines = read_data_file('accept.txt')
+      line_no = 1
+      return lines.collect { |line|
+        pair = [line_no, line]
+        line_no += 1
+        pair
+      }
+    end
+
+    def chunk(lines)
+      # Return groups of lines separated by whitespace or comments
+      #
+      # [(int, str)] -> [[(int, str)]]
+      chunks = []
+      chunk = []
+      lines.each { |lineno, line|
+        stripped = line.strip()
+        if (stripped == '') or stripped.starts_with?('#')
+          if chunk.length > 0
+            chunks << chunk
+            chunk = []
+          end
+        else
+          chunk << [lineno, stripped]
+        end
+      }
+
+      if chunk.length > 0
+        chunks << chunk
+      end
+
+      return chunks
+    end
+
+    def parseLines(chunk)
+      # Take the given chunk of lines and turn it into a test data
+      # dictionary
+      #
+      # [(int, str)] -> {str:(int, str)}
+      items = {}
+      chunk.each { |lineno, line|
+        header, data = line.split(':', 2)
+        header = header.downcase
+        items[header] = [lineno, data.strip]
+      }
+      return items
+    end
+
+    def parseAvailable(available_text)
+      # Parse an Available: line's data
+      #
+      # str -> [str]
+      return available_text.split(',', -1).collect { |s| s.strip }
+    end
+
+    def parseExpected(expected_text)
+      # Parse an Expected: line's data
+      #
+      # str -> [(str, float)]
+      expected = []
+      if expected_text != ''
+        expected_text.split(',', -1).each { |chunk|
+          chunk = chunk.strip
+          mtype, qstuff = chunk.split(';', -1)
+          mtype = mtype.strip
+          Util.assert(!mtype.index('/').nil?)
+          qstuff = qstuff.strip
+          q, qstr = qstuff.split('=', -1)
+          Util.assert(q == 'q')
+          qval = qstr.to_f
+          expected << [mtype, qval]
+        }
+      end
+
+      return expected
+    end
+
+    def test_accept_headers
+      lines = getTestData()
+      chunks = chunk(lines)
+      data_sets = chunks.collect { |chunk| parseLines(chunk) }
+      cases = []
+      data_sets.each { |data|
+        lnos = []
+        lno, header = data['accept']
+        lnos << lno
+        lno, avail_data = data['available']
+        lnos << lno
+        begin
+          available = parseAvailable(avail_data)
+        rescue
+          print 'On line', lno
+          raise
+        end
+
+        lno, exp_data = data['expected']
+        lnos << lno
+        begin
+          expected = parseExpected(exp_data)
+        rescue
+          print 'On line', lno
+          raise
+        end
+
+        descr = sprintf('MatchAcceptTest for lines %s', lnos)
+
+        # Test:
+        accepted = Yadis.parse_accept_header(header)
+        actual = Yadis.match_types(accepted, available)
+        assert_equal(expected, actual)
+
+        assert_equal(Yadis.get_acceptable(header, available),
+                     expected.collect { |mtype, _| mtype })
+      }
+    end
+
+    def test_generate_accept_header
+      # TODO: move this into a test case file and write parsing code
+      # for it.
+
+      # Form: [input_array, expected_header_string]
+      cases = [
+               # Empty input list
+               [[], ""],
+               # Content type name only; no q value
+               [["test"], "test"],
+               # q = 1.0 should be omitted from the header
+               [[["test", 1.0]], "test"],
+               # Test conversion of float to string
+               [["test", ["with_q", 0.8]], "with_q; q=0.8, test"],
+               # Allow string q values, too
+               [["test", ["with_q_str", "0.7"]], "with_q_str; q=0.7, test"],
+               # Test q values out of bounds
+               [[["test", -1.0]], nil],
+               [[["test", 1.1]], nil],
+               # Test sorting of types by q value
+               [[["middle", 0.5], ["min", 0.1], "max"],
+                "min; q=0.1, middle; q=0.5, max"],
+
+              ].each { |input, expected_header|
+
+        if expected_header.nil?
+          assert_raise(ArgumentError) {
+            Yadis.generate_accept_header(*input)
+          }
+        else
+          assert_equal(expected_header, Yadis.generate_accept_header(*input),
+                       [input, expected_header].inspect)
+        end
+      }
+    end
+
+  end
+
+end
diff --git a/vendor/gems/ruby-openid-2.1.4/test/test_association.rb b/vendor/gems/ruby-openid-2.1.4/test/test_association.rb
new file mode 100644 (file)
index 0000000..1b27332
--- /dev/null
@@ -0,0 +1,266 @@
+require "test/unit"
+require "openid/association"
+
+module OpenID
+  class AssociationTestCase < Test::Unit::TestCase
+    def setup
+      # Use this funny way of getting a time so that it does not have
+      # fractional seconds, and so can be serialized exactly using our
+      # standard code.
+      issued = Time.at(Time.now.to_i)
+      lifetime = 600
+
+      @assoc = Association.new('handle', 'secret', issued,
+                               lifetime, 'HMAC-SHA1')
+    end
+
+    def test_round_trip
+      assoc2 = Association.deserialize(@assoc.serialize())
+      [:handle, :secret, :lifetime, :assoc_type].each do |attr|
+        assert_equal(@assoc.send(attr), assoc2.send(attr))
+      end
+    end
+
+    def test_deserialize_failure
+      field_list = Util.kv_to_seq(@assoc.serialize)
+      kv = Util.seq_to_kv(field_list + [['monkeys', 'funny']])
+      assert_raises(ProtocolError) {
+        Association.deserialize(kv)
+      }
+
+      bad_version_list = field_list.dup
+      bad_version_list[0] = ['version', 'moon']
+      bad_version_kv = Util.seq_to_kv(bad_version_list)
+      assert_raises(ProtocolError) {
+        Association.deserialize(bad_version_kv)
+      }
+    end
+
+    def test_serialization_identity
+      assoc2 = Association.deserialize(@assoc.serialize)
+      assert_equal(@assoc, assoc2)
+    end
+
+    def test_expires_in
+      # Allow one second of slop
+      assert(@assoc.expires_in.between?(599,600))
+      assert(@assoc.expires_in(Time.now.to_i).between?(599,600))
+      assert_equal(0,@assoc.expires_in(Time.now.to_i + 10000),"negative expires_in")
+    end
+
+    def test_from_expires_in
+      start_time = Time.now
+      expires_in = @assoc.expires_in
+      assoc = Association.from_expires_in(expires_in,
+                                          @assoc.handle,
+                                          @assoc.secret,
+                                          @assoc.assoc_type)
+
+      # Allow one second of slop here for code execution time
+      assert_in_delta(1, assoc.expires_in, @assoc.expires_in)
+      [:handle, :secret, :assoc_type].each do |attr|
+        assert_equal(@assoc.send(attr), assoc.send(attr))
+      end
+
+      # Make sure the issued time is near the start
+      assert(assoc.issued >= start_time)
+      assert_in_delta(1, assoc.issued.to_f, start_time.to_f)
+    end
+
+    def test_sign_sha1
+      pairs = [['key1', 'value1'],
+               ['key2', 'value2']]
+
+      [['HMAC-SHA256', "\xfd\xaa\xfe;\xac\xfc*\x988\xad\x05d6-"\
+                       "\xeaVy\xd5\xa5Z.<\xa9\xed\x18\x82\\$"\
+                       "\x95x\x1c&"],
+       ['HMAC-SHA1', "\xe0\x1bv\x04\xf1G\xc0\xbb\x7f\x9a\x8b"\
+                     "\xe9\xbc\xee}\\\xe5\xbb7*"],
+      ].each do |assoc_type, expected|
+        assoc = Association.from_expires_in(3600, "handle", 'very_secret',
+                                            assoc_type)
+        sig = assoc.sign(pairs)
+        assert_equal(sig, expected)
+
+        m = Message.new(OPENID2_NS)
+        pairs.each { |k, v|
+          m.set_arg(OPENID_NS, k, v)
+        }
+        m.set_arg(BARE_NS, "not_an_openid_arg", "bogus")
+
+        signed_m = assoc.sign_message(m)
+        assert(signed_m.has_key?(OPENID_NS, 'sig'))
+        assert_equal(signed_m.get_arg(OPENID_NS, 'signed'),
+                     'assoc_handle,key1,key2,ns,signed')
+      end
+    end
+
+    def test_sign_message_with_sig
+      assoc = Association.from_expires_in(3600, "handle", "very_secret",
+                                          "HMAC-SHA1")
+      m = Message.new(OPENID2_NS)
+      m.set_arg(OPENID_NS, 'sig', 'noise')
+      assert_raises(ArgumentError) {
+        assoc.sign_message(m)
+      }
+    end
+
+    def test_sign_message_with_signed
+      assoc = Association.from_expires_in(3600, "handle", "very_secret",
+                                          "HMAC-SHA1")
+      m = Message.new(OPENID2_NS)
+      m.set_arg(OPENID_NS, 'signed', 'fields')
+      assert_raises(ArgumentError) {
+        assoc.sign_message(m)
+      }
+    end
+
+    def test_sign_different_assoc_handle
+      assoc = Association.from_expires_in(3600, "handle", "very_secret",
+                                          "HMAC-SHA1")
+      m = Message.new(OPENID2_NS)
+      m.set_arg(OPENID_NS, 'assoc_handle', 'different')
+      assert_raises(ArgumentError) {
+        assoc.sign_message(m)
+      }
+    end
+
+    def test_sign_bad_assoc_type
+      @assoc.instance_eval { @assoc_type = 'Cookies' }
+      assert_raises(ProtocolError) {
+        @assoc.sign([])
+      }
+    end
+
+    def test_make_pairs
+      msg = Message.new(OPENID2_NS)
+      msg.update_args(OPENID2_NS, {
+                        'mode' => 'id_res',
+                        'identifier' => '=example',
+                        'signed' => 'identifier,mode',
+                        'sig' => 'cephalopod',
+                      })
+      msg.update_args(BARE_NS, {'xey' => 'value'})
+      assoc = Association.from_expires_in(3600, '{sha1}', 'very_secret',
+                                          "HMAC-SHA1")
+      pairs = assoc.make_pairs(msg)
+      assert_equal([['identifier', '=example'],
+                    ['mode', 'id_res']], pairs)
+    end
+
+    def test_check_message_signature_no_signed
+      m = Message.new(OPENID2_NS)
+      m.update_args(OPENID2_NS, {'mode' => 'id_res',
+                      'identifier' => '=example',
+                      'sig' => 'coyote',
+                    })
+      assoc = Association.from_expires_in(3600, '{sha1}', 'very_secret',
+                                          "HMAC-SHA1")
+      assert_raises(ProtocolError) {
+        assoc.check_message_signature(m)
+      }
+    end
+
+    def test_check_message_signature_no_sig
+      m = Message.new(OPENID2_NS)
+      m.update_args(OPENID2_NS, {'mode' => 'id_res',
+                      'identifier' => '=example',
+                      'signed' => 'mode',
+                    })
+      assoc = Association.from_expires_in(3600, '{sha1}', 'very_secret',
+                                          "HMAC-SHA1")
+      assert_raises(ProtocolError) {
+        assoc.check_message_signature(m)
+      }
+    end
+
+    def test_check_message_signature_bad_sig
+      m = Message.new(OPENID2_NS)
+      m.update_args(OPENID2_NS, {'mode' => 'id_res',
+                      'identifier' => '=example',
+                      'signed' => 'mode',
+                      'sig' => Util.to_base64('coyote'),
+                    })
+      assoc = Association.from_expires_in(3600, '{sha1}', 'very_secret',
+                                          "HMAC-SHA1")
+      assert(!assoc.check_message_signature(m))
+    end
+
+    def test_check_message_signature_good_sig
+      m = Message.new(OPENID2_NS)
+      m.update_args(OPENID2_NS, {'mode' => 'id_res',
+                      'identifier' => '=example',
+                      'signed' => 'mode',
+                      'sig' => Util.to_base64('coyote'),
+                    })
+      assoc = Association.from_expires_in(3600, '{sha1}', 'very_secret',
+                                          "HMAC-SHA1")
+      class << assoc
+        # Override sign, because it's already tested elsewhere
+        def sign(pairs)
+          "coyote"
+        end
+      end
+
+      assert(assoc.check_message_signature(m))
+    end
+  end
+
+  class AssociationNegotiatorTestCase < Test::Unit::TestCase
+    def assert_equal_under(item1, item2)
+      val1 = yield(item1)
+      val2 = yield(item2)
+      assert_equal(val1, val2)
+    end
+
+    def test_copy
+      neg = AssociationNegotiator.new([['HMAC-SHA1', 'DH-SHA1']])
+      neg2 = neg.copy
+      assert_equal_under(neg, neg2) {|n| n.instance_eval{@allowed_types} }
+      assert(neg.object_id != neg2.object_id)
+    end
+
+    def test_add_allowed
+      neg = AssociationNegotiator.new([])
+      assert(!neg.allowed?('HMAC-SHA1', 'DH-SHA1'))
+      assert(!neg.allowed?('HMAC-SHA1', 'no-encryption'))
+      assert(!neg.allowed?('HMAC-SHA256', 'DH-SHA256'))
+      assert(!neg.allowed?('HMAC-SHA256', 'no-encryption'))
+      neg.add_allowed_type('HMAC-SHA1')
+      assert(neg.allowed?('HMAC-SHA1', 'DH-SHA1'))
+      assert(neg.allowed?('HMAC-SHA1', 'no-encryption'))
+      assert(!neg.allowed?('HMAC-SHA256', 'DH-SHA256'))
+      assert(!neg.allowed?('HMAC-SHA256', 'no-encryption'))
+      neg.add_allowed_type('HMAC-SHA256', 'DH-SHA256')
+      assert(neg.allowed?('HMAC-SHA1', 'DH-SHA1'))
+      assert(neg.allowed?('HMAC-SHA1', 'no-encryption'))
+      assert(neg.allowed?('HMAC-SHA256', 'DH-SHA256'))
+      assert(!neg.allowed?('HMAC-SHA256', 'no-encryption'))
+      assert_equal(neg.get_allowed_type, ['HMAC-SHA1', 'DH-SHA1'])
+    end
+
+    def test_bad_assoc_type
+      assert_raises(ProtocolError) {
+        AssociationNegotiator.new([['OMG', 'Ponies']])
+      }
+    end
+
+    def test_bad_session_type
+      assert_raises(ProtocolError) {
+        AssociationNegotiator.new([['HMAC-SHA1', 'OMG-Ponies']])
+      }
+    end
+
+    def test_default_negotiator
+      assert_equal(DefaultNegotiator.get_allowed_type,
+                   ['HMAC-SHA1', 'DH-SHA1'])
+      assert(DefaultNegotiator.allowed?('HMAC-SHA256', 'no-encryption'))
+    end
+
+    def test_encrypted_negotiator
+      assert_equal(EncryptedNegotiator.get_allowed_type,
+                   ['HMAC-SHA1', 'DH-SHA1'])
+      assert(!EncryptedNegotiator.allowed?('HMAC-SHA256', 'no-encryption'))
+    end
+  end
+end
diff --git a/vendor/gems/ruby-openid-2.1.4/test/test_associationmanager.rb b/vendor/gems/ruby-openid-2.1.4/test/test_associationmanager.rb
new file mode 100644 (file)
index 0000000..2a17a0a
--- /dev/null
@@ -0,0 +1,917 @@
+require "openid/consumer/associationmanager"
+require "openid/association"
+require "openid/dh"
+require "openid/util"
+require "openid/cryptutil"
+require "openid/message"
+require "openid/store/memory"
+require "test/unit"
+require "util"
+require "time"
+
+module OpenID
+  class DHAssocSessionTest < Test::Unit::TestCase
+    def test_sha1_get_request
+      # Initialized without an explicit DH gets defaults
+      sess = Consumer::DiffieHellmanSHA1Session.new
+      assert_equal(['dh_consumer_public'], sess.get_request.keys)
+      assert_nothing_raised do
+        Util::from_base64(sess.get_request['dh_consumer_public'])
+      end
+    end
+
+    def test_sha1_get_request_custom_dh
+      dh = DiffieHellman.new(1299721, 2)
+      sess = Consumer::DiffieHellmanSHA1Session.new(dh)
+      req = sess.get_request
+      assert_equal(['dh_consumer_public', 'dh_modulus', 'dh_gen'].sort,
+                   req.keys.sort)
+      assert_equal(dh.modulus, CryptUtil.base64_to_num(req['dh_modulus']))
+      assert_equal(dh.generator, CryptUtil.base64_to_num(req['dh_gen']))
+      assert_nothing_raised do
+        Util::from_base64(req['dh_consumer_public'])
+      end
+    end
+  end
+
+  module TestDiffieHellmanResponseParametersMixin
+    def setup
+      session_cls = self.class.session_cls
+
+      # Pre-compute DH with small prime so tests run quickly.
+      @server_dh = DiffieHellman.new(100389557, 2)
+      @consumer_dh = DiffieHellman.new(100389557, 2)
+
+      # base64(btwoc(g ^ xb mod p))
+      @dh_server_public = CryptUtil.num_to_base64(@server_dh.public)
+
+      @secret = CryptUtil.random_string(session_cls.secret_size)
+
+      enc_mac_key_unencoded =
+        @server_dh.xor_secret(session_cls.hashfunc,
+                              @consumer_dh.public,
+                              @secret)
+
+      @enc_mac_key = Util.to_base64(enc_mac_key_unencoded)
+
+      @consumer_session = session_cls.new(@consumer_dh)
+
+      @msg = Message.new(self.class.message_namespace)
+    end
+
+    def test_extract_secret
+      @msg.set_arg(OPENID_NS, 'dh_server_public', @dh_server_public)
+      @msg.set_arg(OPENID_NS, 'enc_mac_key', @enc_mac_key)
+
+      extracted = @consumer_session.extract_secret(@msg)
+      assert_equal(extracted, @secret)
+    end
+
+    def test_absent_serve_public
+      @msg.set_arg(OPENID_NS, 'enc_mac_key', @enc_mac_key)
+
+      assert_raises(Message::KeyNotFound) {
+        @consumer_session.extract_secret(@msg)
+      }
+    end
+
+    def test_absent_mac_key
+      @msg.set_arg(OPENID_NS, 'dh_server_public', @dh_server_public)
+
+      assert_raises(Message::KeyNotFound) {
+        @consumer_session.extract_secret(@msg)
+      }
+    end
+
+    def test_invalid_base64_public
+      @msg.set_arg(OPENID_NS, 'dh_server_public', 'n o t b a s e 6 4.')
+      @msg.set_arg(OPENID_NS, 'enc_mac_key', @enc_mac_key)
+
+      assert_raises(ArgumentError) {
+        @consumer_session.extract_secret(@msg)
+      }
+    end
+
+    def test_invalid_base64_mac_key
+      @msg.set_arg(OPENID_NS, 'dh_server_public', @dh_server_public)
+      @msg.set_arg(OPENID_NS, 'enc_mac_key', 'n o t base 64')
+
+      assert_raises(ArgumentError) {
+        @consumer_session.extract_secret(@msg)
+      }
+    end
+  end
+
+  class TestConsumerOpenID1DHSHA1 < Test::Unit::TestCase
+    include TestDiffieHellmanResponseParametersMixin
+    class << self
+      attr_reader :session_cls, :message_namespace
+    end
+
+    @session_cls = Consumer::DiffieHellmanSHA1Session
+    @message_namespace = OPENID1_NS
+  end
+
+  class TestConsumerOpenID2DHSHA1 < Test::Unit::TestCase
+    include TestDiffieHellmanResponseParametersMixin
+    class << self
+      attr_reader :session_cls, :message_namespace
+    end
+
+    @session_cls = Consumer::DiffieHellmanSHA1Session
+    @message_namespace = OPENID2_NS
+  end
+
+  class TestConsumerOpenID2DHSHA256 < Test::Unit::TestCase
+    include TestDiffieHellmanResponseParametersMixin
+    class << self
+      attr_reader :session_cls, :message_namespace
+    end
+
+    @session_cls = Consumer::DiffieHellmanSHA256Session
+    @message_namespace = OPENID2_NS
+  end
+
+  class TestConsumerNoEncryptionSession < Test::Unit::TestCase
+    def setup
+      @sess = Consumer::NoEncryptionSession.new
+    end
+
+    def test_empty_request
+      assert_equal(@sess.get_request, {})
+    end
+
+    def test_get_secret
+      secret = 'shhh!' * 4
+      mac_key = Util.to_base64(secret)
+      msg = Message.from_openid_args({'mac_key' => mac_key})
+      assert_equal(secret, @sess.extract_secret(msg))
+    end
+  end
+
+  class TestCreateAssociationRequest < Test::Unit::TestCase
+    def setup
+      @server_url = 'http://invalid/'
+      @assoc_manager = Consumer::AssociationManager.new(nil, @server_url)
+      class << @assoc_manager
+        def compatibility_mode=(val)
+            @compatibility_mode = val
+        end
+      end
+      @assoc_type = 'HMAC-SHA1'
+    end
+
+    def test_no_encryption_sends_type
+      session_type = 'no-encryption'
+      session, args = @assoc_manager.send(:create_associate_request,
+                                          @assoc_type,
+                                          session_type)
+
+      assert(session.is_a?(Consumer::NoEncryptionSession))
+      expected = Message.from_openid_args(
+            {'ns' => OPENID2_NS,
+             'session_type' => session_type,
+             'mode' => 'associate',
+             'assoc_type' => @assoc_type,
+             })
+
+      assert_equal(expected, args)
+    end
+
+    def test_no_encryption_compatibility
+      @assoc_manager.compatibility_mode = true
+      session_type = 'no-encryption'
+      session, args = @assoc_manager.send(:create_associate_request,
+                                          @assoc_type,
+                                          session_type)
+
+      assert(session.is_a?(Consumer::NoEncryptionSession))
+      assert_equal(Message.from_openid_args({'mode' => 'associate',
+                                              'assoc_type' => @assoc_type,
+                                            }), args)
+    end
+
+    def test_dh_sha1_compatibility
+      @assoc_manager.compatibility_mode = true
+      session_type = 'DH-SHA1'
+      session, args = @assoc_manager.send(:create_associate_request,
+                                          @assoc_type,
+                                          session_type)
+
+
+      assert(session.is_a?(Consumer::DiffieHellmanSHA1Session))
+
+      # This is a random base-64 value, so just check that it's
+      # present.
+      assert_not_nil(args.get_arg(OPENID1_NS, 'dh_consumer_public'))
+      args.del_arg(OPENID1_NS, 'dh_consumer_public')
+
+      # OK, session_type is set here and not for no-encryption
+      # compatibility
+      expected = Message.from_openid_args({'mode' => 'associate',
+                                            'session_type' => 'DH-SHA1',
+                                            'assoc_type' => @assoc_type,
+                                          })
+      assert_equal(expected, args)
+    end
+  end
+
+  class TestAssociationManagerExpiresIn < Test::Unit::TestCase
+    def expires_in_msg(val)
+      msg = Message.from_openid_args({'expires_in' => val})
+      Consumer::AssociationManager.extract_expires_in(msg)
+    end
+
+    def test_parse_fail
+      ['',
+       '-2',
+       ' 1',
+       ' ',
+       '0x00',
+       'foosball',
+       '1\n',
+       '100,000,000,000',
+      ].each do |x|
+        assert_raises(ProtocolError) {expires_in_msg(x)}
+      end
+    end
+
+    def test_parse
+      ['0',
+       '1',
+       '1000',
+       '9999999',
+       '01',
+      ].each do |n|
+        assert_equal(n.to_i, expires_in_msg(n))
+      end
+    end
+  end
+
+  class TestAssociationManagerCreateSession < Test::Unit::TestCase
+    def test_invalid
+      assert_raises(ArgumentError) {
+        Consumer::AssociationManager.create_session('monkeys')
+      }
+    end
+
+    def test_sha256
+      sess = Consumer::AssociationManager.create_session('DH-SHA256')
+      assert(sess.is_a?(Consumer::DiffieHellmanSHA256Session))
+    end
+  end
+
+  module NegotiationTestMixin
+    include TestUtil
+    def mk_message(args)
+      args['ns'] = @openid_ns
+      Message.from_openid_args(args)
+    end
+
+    def call_negotiate(responses, negotiator=nil)
+      store = nil
+      compat = self.class::Compat
+      assoc_manager = Consumer::AssociationManager.new(store, @server_url,
+                                                       compat, negotiator)
+      class << assoc_manager
+        attr_accessor :responses
+
+        def request_association(assoc_type, session_type)
+          m = @responses.shift
+          if m.is_a?(Message)
+            raise ServerError.from_message(m)
+          else
+            return m
+          end
+        end
+      end
+      assoc_manager.responses = responses
+      assoc_manager.negotiate_association
+    end
+  end
+
+  # Test the session type negotiation behavior of an OpenID 2
+  # consumer.
+  class TestOpenID2SessionNegotiation < Test::Unit::TestCase
+    include NegotiationTestMixin
+
+    Compat = false
+
+    def setup
+      @server_url = 'http://invalid/'
+      @openid_ns = OPENID2_NS
+    end
+
+    # Test the case where the response to an associate request is a
+    # server error or is otherwise undecipherable.
+    def test_bad_response
+      assert_log_matches('Server error when requesting an association') {
+        assert_equal(call_negotiate([mk_message({})]), nil)
+      }
+    end
+
+    # Test the case where the association type (assoc_type) returned
+    # in an unsupported-type response is absent.
+    def test_empty_assoc_type
+      msg = mk_message({'error' => 'Unsupported type',
+                              'error_code' => 'unsupported-type',
+                              'session_type' => 'new-session-type',
+                            })
+
+      assert_log_matches('Unsupported association type',
+                         "Server #{@server_url} responded with unsupported "\
+                         "association session but did not supply a fallback."
+                         ) {
+        assert_equal(call_negotiate([msg]), nil)
+      }
+
+    end
+
+    # Test the case where the session type (session_type) returned
+    # in an unsupported-type response is absent.
+    def test_empty_session_type
+      msg = mk_message({'error' => 'Unsupported type',
+                              'error_code' => 'unsupported-type',
+                              'assoc_type' => 'new-assoc-type',
+                            })
+
+      assert_log_matches('Unsupported association type',
+                         "Server #{@server_url} responded with unsupported "\
+                         "association session but did not supply a fallback."
+                         ) {
+        assert_equal(call_negotiate([msg]), nil)
+      }
+    end
+
+    # Test the case where an unsupported-type response specifies a
+    # preferred (assoc_type, session_type) combination that is not
+    # allowed by the consumer's SessionNegotiator.
+    def test_not_allowed
+      negotiator = AssociationNegotiator.new([])
+      negotiator.instance_eval{
+        @allowed_types = [['assoc_bogus', 'session_bogus']]
+      }
+      msg = mk_message({'error' => 'Unsupported type',
+                              'error_code' => 'unsupported-type',
+                              'assoc_type' => 'not-allowed',
+                              'session_type' => 'not-allowed',
+                            })
+
+      assert_log_matches('Unsupported association type',
+                         'Server sent unsupported session/association type:') {
+        assert_equal(call_negotiate([msg], negotiator), nil)
+      }
+    end
+
+    # Test the case where an unsupported-type response triggers a
+    # retry to get an association with the new preferred type.
+    def test_unsupported_with_retry
+      msg = mk_message({'error' => 'Unsupported type',
+                              'error_code' => 'unsupported-type',
+                              'assoc_type' => 'HMAC-SHA1',
+                              'session_type' => 'DH-SHA1',
+                            })
+
+      assoc = Association.new('handle', 'secret', Time.now, 10000, 'HMAC-SHA1')
+
+      assert_log_matches('Unsupported association type') {
+        assert_equal(assoc, call_negotiate([msg, assoc]))
+      }
+    end
+
+    # Test the case where an unsupported-typ response triggers a
+    # retry, but the retry fails and nil is returned instead.
+    def test_unsupported_with_retry_and_fail
+      msg = mk_message({'error' => 'Unsupported type',
+                              'error_code' => 'unsupported-type',
+                              'assoc_type' => 'HMAC-SHA1',
+                              'session_type' => 'DH-SHA1',
+                            })
+
+      assert_log_matches('Unsupported association type',
+                         "Server #{@server_url} refused") {
+        assert_equal(call_negotiate([msg, msg]), nil)
+      }
+    end
+
+    # Test the valid case, wherein an association is returned on the
+    # first attempt to get one.
+    def test_valid
+      assoc = Association.new('handle', 'secret', Time.now, 10000, 'HMAC-SHA1')
+
+      assert_log_matches() {
+        assert_equal(call_negotiate([assoc]), assoc)
+      }
+    end
+  end
+
+
+  # Tests for the OpenID 1 consumer association session behavior.  See
+  # the docs for TestOpenID2SessionNegotiation.  Notice that this
+  # class is not a subclass of the OpenID 2 tests.  Instead, it uses
+  # many of the same inputs but inspects the log messages logged with
+  # oidutil.log.  See the calls to self.failUnlessLogMatches.  Some of
+  # these tests pass openid2-style messages to the openid 1
+  # association processing logic to be sure it ignores the extra data.
+  class TestOpenID1SessionNegotiation < Test::Unit::TestCase
+    include NegotiationTestMixin
+
+    Compat = true
+
+    def setup
+      @server_url = 'http://invalid/'
+      @openid_ns = OPENID1_NS
+    end
+
+    def test_bad_response
+      assert_log_matches('Server error when requesting an association') {
+        response = call_negotiate([mk_message({})])
+        assert_equal(nil, response)
+      }
+    end
+
+    def test_empty_assoc_type
+      msg = mk_message({'error' => 'Unsupported type',
+                         'error_code' => 'unsupported-type',
+                         'session_type' => 'new-session-type',
+                       })
+
+      assert_log_matches('Server error when requesting an association') {
+        response = call_negotiate([msg])
+        assert_equal(nil, response)
+      }
+    end
+
+    def test_empty_session_type
+      msg = mk_message({'error' => 'Unsupported type',
+                         'error_code' => 'unsupported-type',
+                         'assoc_type' => 'new-assoc-type',
+                       })
+
+      assert_log_matches('Server error when requesting an association') {
+        response = call_negotiate([msg])
+        assert_equal(nil, response)
+      }
+    end
+
+    def test_not_allowed
+      negotiator = AssociationNegotiator.new([])
+      negotiator.instance_eval{
+        @allowed_types = [['assoc_bogus', 'session_bogus']]
+      }
+
+      msg = mk_message({'error' => 'Unsupported type',
+                         'error_code' => 'unsupported-type',
+                         'assoc_type' => 'not-allowed',
+                         'session_type' => 'not-allowed',
+                       })
+
+      assert_log_matches('Server error when requesting an association') {
+        response = call_negotiate([msg])
+        assert_equal(nil, response)
+      }
+    end
+
+    def test_unsupported_with_retry
+      msg = mk_message({'error' => 'Unsupported type',
+                         'error_code' => 'unsupported-type',
+                         'assoc_type' => 'HMAC-SHA1',
+                         'session_type' => 'DH-SHA1',
+                       })
+
+      assoc = Association.new('handle', 'secret', Time.now, 10000, 'HMAC-SHA1')
+
+
+      assert_log_matches('Server error when requesting an association') {
+        response = call_negotiate([msg, assoc])
+        assert_equal(nil, response)
+      }
+    end
+
+    def test_valid
+      assoc = Association.new('handle', 'secret', Time.now, 10000, 'HMAC-SHA1')
+      assert_log_matches() {
+        response = call_negotiate([assoc])
+        assert_equal(assoc, response)
+      }
+    end
+  end
+
+
+  class TestExtractAssociation < Test::Unit::TestCase
+    include ProtocolErrorMixin
+
+    # An OpenID associate response (without the namespace)
+    DEFAULTS = {
+      'expires_in' => '1000',
+      'assoc_handle' => 'a handle',
+      'assoc_type' => 'a type',
+      'session_type' => 'a session type',
+    }
+
+    def setup
+      @assoc_manager = Consumer::AssociationManager.new(nil, nil)
+    end
+
+    # Make tests that ensure that an association response that is
+    # missing required fields will raise an Message::KeyNotFound.
+    #
+    # According to 'Association Session Response' subsection 'Common
+    # Response Parameters', the following fields are required for
+    # OpenID 2.0:
+    #
+    #  * ns
+    #  * session_type
+    #  * assoc_handle
+    #  * assoc_type
+    #  * expires_in
+    #
+    # In OpenID 1, everything except 'session_type' and 'ns' are
+    # required.
+    MISSING_FIELD_SETS = ([["no_fields", []]] +
+                          (DEFAULTS.keys.map do |f|
+                             fields = DEFAULTS.keys
+                             fields.delete(f)
+                             ["missing_#{f}", fields]
+                           end)
+                          )
+
+    [OPENID1_NS, OPENID2_NS].each do |ns|
+      MISSING_FIELD_SETS.each do |name, fields|
+        # OpenID 1 is allowed to be missing session_type
+        if ns != OPENID1_NS and name != 'missing_session_type'
+          test = lambda do
+            msg = Message.new(ns)
+            fields.each do |field|
+              msg.set_arg(ns, field, DEFAULTS[field])
+            end
+            assert_raises(Message::KeyNotFound) do
+              @assoc_manager.send(:extract_association, msg, nil)
+            end
+          end
+          define_method("test_#{name}", test)
+        end
+      end
+    end
+
+    # assert that extracting a response that contains the given
+    # response session type when the request was made for the given
+    # request session type will raise a ProtocolError indicating
+    # session type mismatch
+    def assert_session_mismatch(req_type, resp_type, ns)
+      # Create an association session that has "req_type" as its
+      # session_type and no allowed_assoc_types
+      assoc_session_class = Class.new do
+        @session_type = req_type
+        def self.session_type
+          @session_type
+        end
+        def self.allowed_assoc_types
+          []
+        end
+      end
+      assoc_session = assoc_session_class.new
+
+      # Build an OpenID 1 or 2 association response message that has
+      # the specified association session type
+      msg = Message.new(ns)
+      msg.update_args(ns, DEFAULTS)
+      msg.set_arg(ns, 'session_type', resp_type)
+
+      # The request type and response type have been chosen to produce
+      # a session type mismatch.
+      assert_protocol_error('Session type mismatch') {
+        @assoc_manager.send(:extract_association, msg, assoc_session)
+      }
+    end
+
+    [['no-encryption', '', OPENID2_NS],
+     ['DH-SHA1', 'no-encryption', OPENID2_NS],
+     ['DH-SHA256', 'no-encryption', OPENID2_NS],
+     ['no-encryption', 'DH-SHA1', OPENID2_NS],
+     ['DH-SHA1', 'DH-SHA256', OPENID1_NS],
+     ['DH-SHA256', 'DH-SHA1', OPENID1_NS],
+     ['no-encryption', 'DH-SHA1', OPENID1_NS],
+    ].each do |req_type, resp_type, ns|
+      test = lambda { assert_session_mismatch(req_type, resp_type, ns) }
+      name = "test_mismatch_req_#{req_type}_resp_#{resp_type}_#{ns}"
+      define_method(name, test)
+    end
+
+    def test_openid1_no_encryption_fallback
+      # A DH-SHA1 session
+      assoc_session = Consumer::DiffieHellmanSHA1Session.new
+
+      # An OpenID 1 no-encryption association response
+      msg = Message.from_openid_args({
+                                       'expires_in' => '1000',
+                                       'assoc_handle' => 'a handle',
+                                       'assoc_type' => 'HMAC-SHA1',
+                                       'mac_key' => 'X' * 20,
+                                     })
+
+      # Should succeed
+      assoc = @assoc_manager.send(:extract_association, msg, assoc_session)
+      assert_equal('a handle', assoc.handle)
+      assert_equal('HMAC-SHA1', assoc.assoc_type)
+      assert(assoc.expires_in.between?(999, 1000))
+      assert('X' * 20, assoc.secret)
+    end
+  end
+
+  class GetOpenIDSessionTypeTest < Test::Unit::TestCase
+    include TestUtil
+
+    SERVER_URL = 'http://invalid/'
+
+    def do_test(expected_session_type, session_type_value)
+      # Create a Message with just 'session_type' in it, since
+      # that's all this function will use. 'session_type' may be
+      # absent if it's set to None.
+      args = {}
+      if !session_type_value.nil?
+        args['session_type'] = session_type_value
+      end
+      message = Message.from_openid_args(args)
+      assert(message.is_openid1)
+
+      assoc_manager = Consumer::AssociationManager.new(nil, SERVER_URL)
+      actual_session_type = assoc_manager.send(:get_openid1_session_type,
+                                               message)
+      error_message = ("Returned session type parameter #{session_type_value}"\
+                       "was expected to yield session type "\
+                       "#{expected_session_type}, but yielded "\
+                       "#{actual_session_type}")
+      assert_equal(expected_session_type, actual_session_type, error_message)
+    end
+
+
+    [['nil', 'no-encryption', nil],
+     ['empty', 'no-encryption', ''],
+     ['dh_sha1', 'DH-SHA1', 'DH-SHA1'],
+     ['dh_sha256', 'DH-SHA256', 'DH-SHA256'],
+    ].each {|name, expected, input|
+      # Define a test method that will check what session type will be
+      # used if the OpenID 1 response to an associate call sets the
+      # 'session_type' field to `session_type_value`
+      test = lambda {assert_log_matches() { do_test(expected, input) } }
+      define_method("test_#{name}", &test)
+    }
+
+    # This one's different because it expects log messages
+    def test_explicit_no_encryption
+      assert_log_matches("WARNING: #{SERVER_URL} sent 'no-encryption'"){
+        do_test('no-encryption', 'no-encryption')
+      }
+    end
+  end
+
+  class ExtractAssociationTest < Test::Unit::TestCase
+    include ProtocolErrorMixin
+
+    SERVER_URL = 'http://invalid/'
+
+    def setup
+      @session_type = 'testing-session'
+
+      # This must something that works for Association::from_expires_in
+      @assoc_type = 'HMAC-SHA1'
+
+      @assoc_handle = 'testing-assoc-handle'
+
+      # These arguments should all be valid
+      @assoc_response =
+        Message.from_openid_args({
+                                   'expires_in' => '1000',
+                                   'assoc_handle' => @assoc_handle,
+                                   'assoc_type' => @assoc_type,
+                                   'session_type' => @session_type,
+                                   'ns' => OPENID2_NS,
+                                 })
+      assoc_session_cls = Class.new do
+        class << self
+          attr_accessor :allowed_assoc_types, :session_type
+        end
+
+        attr_reader :extract_secret_called, :secret
+        def initialize
+          @extract_secret_called = false
+          @secret = 'shhhhh!'
+        end
+
+        def extract_secret(_)
+          @extract_secret_called = true
+          @secret
+        end
+      end
+      @assoc_session = assoc_session_cls.new
+      @assoc_session.class.allowed_assoc_types = [@assoc_type]
+      @assoc_session.class.session_type = @session_type
+
+      @assoc_manager = Consumer::AssociationManager.new(nil, SERVER_URL)
+    end
+
+    def call_extract
+      @assoc_manager.send(:extract_association,
+                          @assoc_response, @assoc_session)
+    end
+
+    # Handle a full successful association response
+    def test_works_with_good_fields
+      assoc = call_extract
+      assert(@assoc_session.extract_secret_called)
+      assert_equal(@assoc_session.secret, assoc.secret)
+      assert_equal(1000, assoc.lifetime)
+      assert_equal(@assoc_handle, assoc.handle)
+      assert_equal(@assoc_type, assoc.assoc_type)
+    end
+
+    def test_bad_assoc_type
+      # Make sure that the assoc type in the response is not valid
+      # for the given session.
+      @assoc_session.class.allowed_assoc_types = []
+      assert_protocol_error('Unsupported assoc_type for sess') {call_extract}
+    end
+
+    def test_bad_expires_in
+      # Invalid value for expires_in should cause failure
+      @assoc_response.set_arg(OPENID_NS, 'expires_in', 'forever')
+      assert_protocol_error('Invalid expires_in') {call_extract}
+    end
+  end
+
+  class TestExtractAssociationDiffieHellman < Test::Unit::TestCase
+    include ProtocolErrorMixin
+
+    SECRET = 'x' * 20
+
+    def setup
+      @assoc_manager = Consumer::AssociationManager.new(nil, nil)
+    end
+
+    def setup_dh
+      sess, message = @assoc_manager.send(:create_associate_request,
+                                          'HMAC-SHA1', 'DH-SHA1')
+
+      server_dh = DiffieHellman.new
+      cons_dh = sess.instance_variable_get('@dh')
+
+      enc_mac_key = server_dh.xor_secret(CryptUtil.method(:sha1),
+                                         cons_dh.public, SECRET)
+
+      server_resp = {
+        'dh_server_public' => CryptUtil.num_to_base64(server_dh.public),
+        'enc_mac_key' => Util.to_base64(enc_mac_key),
+        'assoc_type' => 'HMAC-SHA1',
+        'assoc_handle' => 'handle',
+        'expires_in' => '1000',
+        'session_type' => 'DH-SHA1',
+      }
+      if @assoc_manager.instance_variable_get(:@compatibility_mode)
+        server_resp['ns'] = OPENID2_NS
+      end
+      return [sess, Message.from_openid_args(server_resp)]
+    end
+
+    def test_success
+      sess, server_resp = setup_dh
+      ret = @assoc_manager.send(:extract_association, server_resp, sess)
+      assert(!ret.nil?)
+      assert_equal(ret.assoc_type, 'HMAC-SHA1')
+      assert_equal(ret.secret, SECRET)
+      assert_equal(ret.handle, 'handle')
+      assert_equal(ret.lifetime, 1000)
+    end
+
+    def test_openid2success
+      # Use openid 1 type in endpoint so _setUpDH checks
+      # compatibility mode state properly
+      @assoc_manager.instance_variable_set('@compatibility_mode', true)
+      test_success()
+    end
+
+    def test_bad_dh_values
+      sess, server_resp = setup_dh
+      server_resp.set_arg(OPENID_NS, 'enc_mac_key', '\x00\x00\x00')
+      assert_protocol_error('Malformed response for') {
+        @assoc_manager.send(:extract_association, server_resp, sess)
+      }
+    end
+  end
+
+  class TestAssocManagerGetAssociation < Test::Unit::TestCase
+    include FetcherMixin
+    include TestUtil
+
+    attr_reader :negotiate_association
+
+    def setup
+      @server_url = 'http://invalid/'
+      @store = Store::Memory.new
+      @assoc_manager = Consumer::AssociationManager.new(@store, @server_url)
+      @assoc_manager.extend(Const)
+      @assoc = Association.new('handle', 'secret', Time.now, 10000,
+                               'HMAC-SHA1')
+    end
+
+    def set_negotiate_response(assoc)
+      @assoc_manager.const(:negotiate_association, assoc)
+    end
+
+    def test_not_in_store_no_response
+      set_negotiate_response(nil)
+      assert_equal(nil, @assoc_manager.get_association)
+    end
+
+    def test_not_in_store_negotiate_assoc
+      # Not stored beforehand:
+      stored_assoc = @store.get_association(@server_url, @assoc.handle)
+      assert_equal(nil, stored_assoc)
+
+      # Returned from associate call:
+      set_negotiate_response(@assoc)
+      assert_equal(@assoc, @assoc_manager.get_association)
+
+      # It should have been stored:
+      stored_assoc = @store.get_association(@server_url, @assoc.handle)
+      assert_equal(@assoc, stored_assoc)
+    end
+
+    def test_in_store_no_response
+      set_negotiate_response(nil)
+      @store.store_association(@server_url, @assoc)
+      assert_equal(@assoc, @assoc_manager.get_association)
+    end
+
+    def test_request_assoc_with_status_error
+      fetcher_class = Class.new do
+        define_method(:fetch) do |*args|
+          MockResponse.new(500, '')
+        end
+      end
+      with_fetcher(fetcher_class.new) do
+        assert_log_matches('Got HTTP status error when requesting') {
+          result = @assoc_manager.send(:request_association, 'HMAC-SHA1',
+                                       'no-encryption')
+          assert(result.nil?)
+        }
+      end
+    end
+  end
+
+  class TestAssocManagerRequestAssociation < Test::Unit::TestCase
+    include FetcherMixin
+    include TestUtil
+
+    def setup
+      @assoc_manager = Consumer::AssociationManager.new(nil, 'http://invalid/')
+      @assoc_type = 'HMAC-SHA1'
+      @session_type = 'no-encryption'
+      @message = Message.new(OPENID2_NS)
+      @message.update_args(OPENID_NS, {
+                             'assoc_type' => @assoc_type,
+                             'session_type' => @session_type,
+                             'assoc_handle' => 'kaboodle',
+                             'expires_in' => '1000',
+                             'mac_key' => 'X' * 20,
+                           })
+    end
+
+    def make_request
+      kv = @message.to_kvform
+      fetcher_class = Class.new do
+        define_method(:fetch) do |*args|
+          MockResponse.new(200, kv)
+        end
+      end
+      with_fetcher(fetcher_class.new) do
+        @assoc_manager.send(:request_association, @assoc_type, @session_type)
+      end
+    end
+
+    # The association we get is from valid processing of our result,
+    # and that no errors are raised
+    def test_success
+      assert_equal('kaboodle', make_request.handle)
+    end
+
+    # A missing parameter gets translated into a log message and
+    # causes the method to return nil
+    def test_missing_fields
+      @message.del_arg(OPENID_NS, 'assoc_type')
+      assert_log_matches('Missing required par') {
+        assert_equal(nil, make_request)
+      }
+    end
+
+    # A bad value results in a log message and causes the method to
+    # return nil
+    def test_protocol_error
+      @message.set_arg(OPENID_NS, 'expires_in', 'goats')
+      assert_log_matches('Protocol error processing') {
+        assert_equal(nil, make_request)
+      }
+    end
+  end
+
+end
diff --git a/vendor/gems/ruby-openid-2.1.4/test/test_ax.rb b/vendor/gems/ruby-openid-2.1.4/test/test_ax.rb
new file mode 100644 (file)
index 0000000..c1dcfb1
--- /dev/null
@@ -0,0 +1,648 @@
+require 'openid/extensions/ax'
+require 'openid/message'
+require 'openid/consumer/responses'
+require 'openid/consumer/discovery'
+require 'openid/consumer/checkid_request'
+
+module OpenID
+  module AX
+    class BogusAXMessage < AXMessage
+      @mode = 'bogus'
+
+      def get_extension_args
+        new_args
+      end
+
+      def do_check_mode(args)
+        check_mode(args)
+      end
+
+      def do_check_mode_new_args
+        check_mode(new_args)
+      end
+    end
+
+    class AXMessageTest < Test::Unit::TestCase
+      def setup
+        @bax = BogusAXMessage.new
+      end
+
+      def test_check_mode
+        assert_raises(Error) { @bax.do_check_mode({'mode' => 'fetch_request'})}
+        @bax.do_check_mode({'mode' => @bax.mode})
+      end
+
+      def test_check_mode_new_args
+        @bax.do_check_mode_new_args
+      end
+    end
+
+    class AttrInfoTest < Test::Unit::TestCase
+      def test_construct
+        assert_raises(ArgumentError) { AttrInfo.new }
+        type_uri = 'uri geller'
+        ainfo = AttrInfo.new(type_uri)
+
+        assert_equal(type_uri, ainfo.type_uri)
+        assert_equal(1, ainfo.count)
+        assert_equal(false, ainfo.required)
+        assert_equal(nil, ainfo.ns_alias)
+      end
+    end
+
+    class ToTypeURIsTest < Test::Unit::TestCase
+      def setup
+        @aliases = NamespaceMap.new
+      end
+
+      def test_empty
+        [nil, ''].each{|empty|
+          uris = AX.to_type_uris(@aliases, empty)
+          assert_equal([], uris)
+        }
+      end
+
+      def test_undefined
+        assert_raises(IndexError) {
+          AX.to_type_uris(@aliases, 'http://janrain.com/')
+        }
+      end
+
+      def test_one
+        uri = 'http://janrain.com/'
+        name = 'openid_hackers'
+        @aliases.add_alias(uri, name)
+        uris = AX::to_type_uris(@aliases, name)
+        assert_equal([uri], uris)
+      end
+
+      def test_two
+        uri1 = 'http://janrain.com/'
+        name1 = 'openid_hackers'
+        @aliases.add_alias(uri1, name1)
+
+        uri2 = 'http://jyte.com/'
+        name2 = 'openid_hack'
+        @aliases.add_alias(uri2, name2)
+
+        uris = AX.to_type_uris(@aliases, [name1, name2].join(','))
+        assert_equal([uri1, uri2], uris)
+      end
+    end
+
+    class ParseAXValuesTest < Test::Unit::TestCase
+      def ax_values(ax_args, expected_args)
+        msg = KeyValueMessage.new
+        msg.parse_extension_args(ax_args)
+        assert_equal(expected_args, msg.data)
+      end
+
+      def ax_error(ax_args, error)
+        msg = KeyValueMessage.new
+        assert_raises(error) {
+          msg.parse_extension_args(ax_args)
+        }
+      end
+
+      def test_empty_is_valid
+        ax_values({}, {})
+      end
+
+      def test_missing_value_for_alias_explodes
+        ax_error({'type.foo'=>'urn:foo'}, IndexError)
+      end
+
+      def test_count_present_but_not_value
+        ax_error({'type.foo'=>'urn:foo', 'count.foo' => '1'}, IndexError)
+      end
+
+      def test_invalid_count_value
+        msg = FetchRequest.new
+        assert_raises(Error) {
+          msg.parse_extension_args({'type.foo'=>'urn:foo', 
+                                     'count.foo' => 'bogus'})
+        }
+      end
+
+      def test_request_unlimited_values
+        msg = FetchRequest.new
+        args = {'mode' => 'fetch_request',
+          'required' => 'foo',
+          'type.foo' => 'urn:foo',
+          'count.foo' => UNLIMITED_VALUES
+        }
+        msg.parse_extension_args(args)
+        foo = msg.attributes[0]
+        assert_equal(UNLIMITED_VALUES, foo.count)
+        assert(foo.wants_unlimited_values?)
+      end
+
+      def test_long_alias
+        # spec says we must support at least 32 character-long aliases
+        name = 'x' * MINIMUM_SUPPORTED_ALIAS_LENGTH
+
+        msg = KeyValueMessage.new
+        args = {
+          "type.#{name}" => 'urn:foo',
+          "count.#{name}" => '1',
+          "value.#{name}.1" => 'first',
+        }
+        msg.parse_extension_args(args)
+        assert_equal(['first'],msg['urn:foo'])
+      end
+
+      def test_invalid_alias
+        types = [
+                 KeyValueMessage,
+                 FetchRequest
+                ]
+        inputs = [
+                  {'type.a.b'=>'urn:foo',
+                    'count.a.b'=>'1'},
+                  {'type.a,b'=>'urn:foo',
+                    'count.a,b'=>'1'},
+                 ]
+        types.each{|t|
+          inputs.each{|input|
+            msg = t.new
+            assert_raises(Error) {msg.parse_extension_args(input)}
+          }
+        }
+      end
+
+      def test_count_present_and_is_zero
+        ax_values(
+                  {'type.foo'=>'urn:foo',
+                    'count.foo'=>'0',
+                  },
+                  {'urn:foo'=>[]}
+                  )
+      end
+
+      def test_singleton_empty
+        ax_values(
+                  {'type.foo'=>'urn:foo',
+                    'value.foo'=>'',
+                  },
+                  {'urn:foo'=>[]}
+                  )
+      end
+
+      def test_double_alias
+        ax_error(
+                 {'type.foo'=>'urn:foo',
+                   'value.foo'=>'',
+                   'type.bar'=>'urn:foo',
+                   'value.bar'=>'',
+                 },
+                 IndexError
+                 )
+      end
+
+      def test_double_singleton
+        ax_values(
+                  {'type.foo'=>'urn:foo',
+                    'value.foo'=>'',
+                    'type.bar'=>'urn:bar',
+                    'value.bar'=>'',
+                  },
+                  {'urn:foo'=>[],'urn:bar'=>[]}
+                  )
+      end
+
+      def singleton_value
+        ax_values(
+                  {'type.foo'=>'urn:foo',
+                    'value.foo'=>'something',
+                  },
+                  {'urn:foo'=>['something']}
+                  )     
+      end
+    end
+
+    class FetchRequestTest < Test::Unit::TestCase
+      def setup
+        @msg = FetchRequest.new
+        @type_a = 'http://janrain.example.com/a'
+        @name_a = 'a'
+      end
+
+      def test_mode
+        assert_equal('fetch_request', @msg.mode)
+      end
+
+      def test_construct
+        assert_equal({}, @msg.requested_attributes)
+        assert_equal(nil, @msg.update_url)
+
+        msg = FetchRequest.new('hailstorm')
+        assert_equal({}, msg.requested_attributes)
+        assert_equal('hailstorm', msg.update_url)
+      end
+
+      def test_add
+        uri = 'mud://puddle'
+
+        assert(! @msg.member?(uri))
+        a = AttrInfo.new(uri)
+        @msg.add(a)
+        assert(@msg.member?(uri))
+      end
+
+      def test_add_twice
+        uri = 'its://raining'
+        a = AttrInfo.new(uri)
+        @msg.add(a)
+        assert_raises(IndexError) {@msg.add(a)}
+      end
+
+      def do_extension_args(expected_args)
+        expected_args['mode'] = @msg.mode
+        assert_equal(expected_args, @msg.get_extension_args)
+      end
+
+      def test_get_extension_args_empty
+        do_extension_args({})
+      end
+
+      def test_get_extension_args_no_alias
+        a = AttrInfo.new('foo://bar')
+        @msg.add(a)
+        ax_args = @msg.get_extension_args
+        ax_args.each{|k,v|
+          if v == a.type_uri and k.index('type.') == 0
+            @name = k[5..-1]
+            break
+          end
+        }
+        do_extension_args({'type.'+@name => a.type_uri,
+                            'if_available' => @name})
+      end
+
+      def test_get_extension_args_alias_if_available
+        a = AttrInfo.new('type://of.transportation',
+                         'transport')
+        @msg.add(a)
+        do_extension_args({'type.'+a.ns_alias => a.type_uri,
+                            'if_available' => a.ns_alias})
+      end
+
+      def test_get_extension_args_alias_req
+        a = AttrInfo.new('type://of.transportation',
+                         'transport',
+                         true)
+        @msg.add(a)
+        do_extension_args({'type.'+a.ns_alias => a.type_uri,
+                            'required' => a.ns_alias})
+      end
+
+      def test_get_required_attrs_empty
+        assert_equal([], @msg.get_required_attrs)
+      end
+
+      def test_parse_extension_args_extra_type
+        args = {
+          'mode' => 'fetch_request',
+          'type.' + @name_a => @type_a
+        }
+        assert_raises(Error) {@msg.parse_extension_args(args)}
+      end
+
+      def test_parse_extension_args
+        args = {
+          'mode' => 'fetch_request',
+          'type.' + @name_a => @type_a,
+          'if_available' => @name_a
+        }
+        @msg.parse_extension_args(args)
+        assert(@msg.member?(@type_a) )
+        assert_equal([@type_a], @msg.requested_types)
+        ai = @msg.requested_attributes[@type_a]
+        assert(ai.is_a?(AttrInfo))
+        assert(!ai.required)
+        assert_equal(@type_a, ai.type_uri)
+        assert_equal(@name_a, ai.ns_alias)
+        assert_equal([ai], @msg.attributes)
+      end
+
+      def test_extension_args_idempotent
+        args = {
+          'mode' => 'fetch_request',
+          'type.' + @name_a => @type_a,
+          'if_available' => @name_a
+        }
+        @msg.parse_extension_args(args)
+        assert_equal(args, @msg.get_extension_args)
+        assert(!@msg.requested_attributes[@type_a].required)
+      end
+
+      def test_extension_args_idempotent_count_required
+        args = {
+          'mode' => 'fetch_request',
+          'type.' + @name_a => @type_a,
+          'count.' + @name_a => '2',
+          'required' => @name_a
+        }
+        @msg.parse_extension_args(args)
+        assert_equal(args, @msg.get_extension_args)
+        assert(@msg.requested_attributes[@type_a].required)
+      end
+
+      def test_extension_args_count1
+        args = {
+          'mode' => 'fetch_request',
+          'type.' + @name_a => @type_a,
+          'count.' + @name_a => '1',
+          'if_available' => @name_a
+        }
+        norm_args = {
+          'mode' => 'fetch_request',
+          'type.' + @name_a => @type_a,
+          'if_available' => @name_a
+        }
+        @msg.parse_extension_args(args)
+        assert_equal(norm_args, @msg.get_extension_args)
+      end
+
+      def test_from_openid_request_no_ax
+        message = Message.new
+        openid_req = Server::OpenIDRequest.new
+        openid_req.message = message
+        ax_req = FetchRequest.from_openid_request(openid_req)
+        assert(ax_req.nil?)
+      end
+
+      def test_openid_update_url_verification_error
+        openid_req_msg = Message.from_openid_args({
+                                                    'mode' => 'checkid_setup',
+                                                    'ns' => OPENID2_NS,
+                                                    'realm' => 'http://example.com/realm',
+                                                    'ns.ax' => AXMessage::NS_URI,
+                                                    'ax.update_url' => 'http://different.site/path',
+                                                    'ax.mode' => 'fetch_request',
+                                                  })
+        openid_req = Server::OpenIDRequest.new
+        openid_req.message = openid_req_msg
+        assert_raises(Error) { 
+          FetchRequest.from_openid_request(openid_req)
+        }
+      end
+
+      def test_openid_no_realm
+        openid_req_msg = Message.from_openid_args({
+                                                    'mode' => 'checkid_setup',
+                                                    'ns' => OPENID2_NS,
+                                                    'ns.ax' => AXMessage::NS_URI,
+                                                    'ax.update_url' => 'http://different.site/path',
+                                                    'ax.mode' => 'fetch_request',
+                                                  })
+        openid_req = Server::OpenIDRequest.new
+        openid_req.message = openid_req_msg
+        assert_raises(Error) { 
+          FetchRequest.from_openid_request(openid_req)
+        }
+      end
+
+      def test_openid_update_url_verification_success
+        openid_req_msg = Message.from_openid_args({
+                                                    'mode' => 'checkid_setup',
+                                                    'ns' => OPENID2_NS,
+                                                    'realm' => 'http://example.com/realm',
+                                                    'ns.ax' => AXMessage::NS_URI,
+                                                    'ax.update_url' => 'http://example.com/realm/update_path',
+                                                    'ax.mode' => 'fetch_request',
+                                                  })
+        openid_req = Server::OpenIDRequest.new
+        openid_req.message = openid_req_msg
+        fr = FetchRequest.from_openid_request(openid_req)
+        assert(fr.is_a?(FetchRequest))
+      end
+
+      def test_openid_update_url_verification_success_return_to
+        openid_req_msg = Message.from_openid_args({
+                                                    'mode' => 'checkid_setup',
+                                                    'ns' => OPENID2_NS,
+                                                    'return_to' => 'http://example.com/realm',
+                                                    'ns.ax' => AXMessage::NS_URI,
+                                                    'ax.update_url' => 'http://example.com/realm/update_path',
+                                                    'ax.mode' => 'fetch_request',
+                                                  })
+        openid_req = Server::OpenIDRequest.new
+        openid_req.message = openid_req_msg
+        fr = FetchRequest.from_openid_request(openid_req)
+        assert(fr.is_a?(FetchRequest))
+      end
+
+      def test_add_extension
+        openid_req_msg = Message.from_openid_args({
+                                                    'mode' => 'checkid_setup',
+                                                    'ns' => OPENID2_NS,
+                                                    'return_to' => 'http://example.com/realm',
+                                                  })
+
+        e = OpenID::OpenIDServiceEndpoint.new
+        openid_req = Consumer::CheckIDRequest.new(nil, e)
+        openid_req.message = openid_req_msg
+
+        fr = FetchRequest.new
+        fr.add(AttrInfo.new("urn:bogus"))
+
+        openid_req.add_extension(fr)
+
+        expected = {
+          'mode' => 'fetch_request',
+          'if_available' => 'ext0',
+          'type.ext0' => 'urn:bogus',
+        }
+
+        expected.each { |k,v|
+          assert(openid_req.message.get_arg(AXMessage::NS_URI, k) == v)
+        }
+      end
+    end
+
+    class FetchResponseTest < Test::Unit::TestCase
+      def setup
+        @msg = FetchResponse.new
+        @value_a = 'commodity'
+        @type_a = 'http://blood.transfusion/'
+        @name_a = 'george'
+        @request_update_url = 'http://some.url.that.is.awesome/'
+      end
+
+      def test_construct
+        assert_equal(nil, @msg.update_url)
+        assert_equal({}, @msg.data)
+      end
+
+      def test_get_extension_args_empty
+        eargs = {
+          'mode' => 'fetch_response'
+        }
+        assert_equal(eargs, @msg.get_extension_args)
+      end
+
+      def test_get_extension_args_empty_request
+        eargs = {
+          'mode' => 'fetch_response'
+        }
+        req = FetchRequest.new
+        assert_equal(eargs, @msg.get_extension_args(req))
+      end
+
+      def test_get_extension_args_empty_request_some
+        uri = 'http://not.found/'
+        name = 'ext0'
+        eargs = {
+          'mode' => 'fetch_response',
+          'type.' + name => uri,
+          'count.' + name => '0'
+        }
+        req = FetchRequest.new
+        req.add(AttrInfo.new(uri))
+        assert_equal(eargs, @msg.get_extension_args(req))
+      end
+
+      def test_update_url_in_response
+        uri = 'http://not.found/'
+        name = 'ext0'
+        eargs = {
+          'mode' => 'fetch_response',
+          'update_url' => @request_update_url,
+          'type.' + name => uri,
+          'count.' + name => '0'
+        }
+        req = FetchRequest.new(@request_update_url)
+        req.add(AttrInfo.new(uri))
+        assert_equal(eargs, @msg.get_extension_args(req))
+      end
+
+      def test_get_extension_args_some_request
+        eargs = {
+          'mode' => 'fetch_response',
+          'type.' + @name_a => @type_a,
+          'value.' + @name_a + '.1' => @value_a,
+          'count.' + @name_a =>  '1'
+        }
+        req = FetchRequest.new
+        req.add(AttrInfo.new(@type_a, @name_a))
+        @msg.add_value(@type_a, @value_a)
+        assert_equal(eargs, @msg.get_extension_args(req))
+      end
+
+      def test_get_extension_args_some_not_request
+        req = FetchRequest.new
+        @msg.add_value(@type_a, @value_a)
+        assert_raises(IndexError) {@msg.get_extension_args(req)}
+      end
+
+      def test_get_single_success
+        req = FetchRequest.new
+        @msg.add_value(@type_a, @value_a)
+        assert_equal(@value_a, @msg.get_single(@type_a))
+      end
+
+      def test_get_single_none
+        assert_equal(nil, @msg.get_single(@type_a))
+      end
+
+      def test_get_single_extra
+        @msg.set_values(@type_a, ['x', 'y'])
+        assert_raises(Error) { @msg.get_single(@type_a) }
+      end
+
+      def test_from_success_response
+        uri = 'http://under.the.sea/'
+        name = 'ext0'
+        value = 'snarfblat'
+
+        m = OpenID::Message.from_openid_args({
+                                               'mode' => 'id_res',
+                                               'ns' => OPENID2_NS,
+                                               'ns.ax' => AXMessage::NS_URI,
+                                               'ax.update_url' => 'http://example.com/realm/update_path',
+                                               'ax.mode' => 'fetch_response',
+                                               'ax.type.' + name => uri,
+                                               'ax.count.' + name => '1',
+                                               'ax.value.' + name + '.1' => value,
+                                             })
+
+        e = OpenID::OpenIDServiceEndpoint.new()
+        resp = OpenID::Consumer::SuccessResponse.new(e, m, [])
+
+        ax_resp = FetchResponse.from_success_response(resp, false)
+
+        values = ax_resp[uri]
+        assert_equal(values, [value])
+      end
+
+      def test_from_success_response_empty
+        e = OpenID::OpenIDServiceEndpoint.new()
+        m = OpenID::Message.from_openid_args({'mode' => 'id_res'})
+        resp = OpenID::Consumer::SuccessResponse.new(e, m, [])
+        ax_resp = FetchResponse.from_success_response(resp)
+        assert(ax_resp.nil?)
+      end
+    end
+
+    class StoreRequestTest < Test::Unit::TestCase
+      def setup
+        @msg = StoreRequest.new
+        @type_a = 'http://oranges.are.for/'
+        @name_a = 'juggling'
+      end
+
+      def test_construct
+        assert_equal({}, @msg.data)
+      end
+
+      def test_get_extension_args_empty
+        eargs = {
+          'mode' => 'store_request'
+        }
+        assert_equal(eargs, @msg.get_extension_args)
+      end
+
+      def test_get_extension_args_nonempty
+        @msg.set_values(@type_a, ['foo','bar'])
+        aliases = NamespaceMap.new
+        aliases.add_alias(@type_a, @name_a)
+        eargs = {
+          'mode' => 'store_request',
+          'type.' + @name_a => @type_a,
+          'value.' + @name_a + '.1' => 'foo',
+          'value.' + @name_a + '.2' => 'bar',
+          'count.' + @name_a =>  '2'
+        }
+        assert_equal(eargs, @msg.get_extension_args(aliases))
+      end
+    end
+
+    class StoreResponseTest < Test::Unit::TestCase
+      def test_success
+        msg = StoreResponse.new
+        assert(msg.succeeded?)
+        assert(!msg.error_message)
+        assert_equal({'mode' => 'store_response_success'}, 
+                     msg.get_extension_args)
+      end
+
+      def test_fail_nomsg
+        msg = StoreResponse.new(false)
+        assert(! msg.succeeded? )
+        assert(! msg.error_message )
+        assert_equal({'mode' => 'store_response_failure'}, 
+                     msg.get_extension_args)
+      end
+
+      def test_fail_msg
+        reason = "because I said so"
+        msg = StoreResponse.new(false, reason)
+        assert(! msg.succeeded? )
+        assert_equal(reason,  msg.error_message)
+        assert_equal({'mode' => 'store_response_failure', 'error' => reason}, 
+                     msg.get_extension_args)
+      end
+    end
+  end
+end
diff --git a/vendor/gems/ruby-openid-2.1.4/test/test_checkid_request.rb b/vendor/gems/ruby-openid-2.1.4/test/test_checkid_request.rb
new file mode 100644 (file)
index 0000000..e1d95c9
--- /dev/null
@@ -0,0 +1,294 @@
+require "openid/consumer/checkid_request"
+require "openid/message"
+require "test/unit"
+require "testutil"
+require "util"
+
+module OpenID
+  class Consumer
+    class CheckIDRequest
+      class DummyEndpoint
+        attr_accessor :preferred_namespace, :local_id, :server_url,
+          :is_op_identifier, :claimed_id
+
+        def initialize
+          @preferred_namespace = nil
+          @local_id = nil
+          @server_url = nil
+          @is_op_identifier = false
+        end
+
+        def get_local_id
+          @local_id
+        end
+
+        def compatibility_mode
+          @preferred_namespace == OPENID1_NS
+        end
+      end
+
+      module CheckIDTestMixin
+        include TestUtil
+
+        def setup
+          @endpoint = DummyEndpoint.new
+          @endpoint.local_id = 'http://server.unittest/joe'
+          @endpoint.claimed_id = 'http://joe.vanity.example/'
+          @endpoint.server_url = 'http://server.unittest/'
+          @endpoint.preferred_namespace = preferred_namespace
+          @realm = 'http://example/'
+          @return_to = 'http://example/return/'
+          @assoc = GoodAssoc.new
+          @checkid_req = CheckIDRequest.new(@assoc, @endpoint)
+        end
+
+        def assert_has_identifiers(msg, local_id, claimed_id)
+          assert_openid_value_equal(msg, 'identity', local_id)
+          assert_openid_value_equal(msg, 'claimed_id', claimed_id)
+        end
+
+        def assert_openid_key_exists(msg, key)
+          assert(msg.get_arg(OPENID_NS, key),
+                 "#{key} not present in #{msg.get_args(OPENID_NS).inspect}")
+        end
+
+        def assert_openid_key_absent(msg, key)
+          assert(msg.get_arg(OPENID_NS, key).nil?)
+        end
+
+        def assert_openid_value_equal(msg, key, expected)
+          actual = msg.get_arg(OPENID_NS, key, NO_DEFAULT)
+          error_text = ("Expected #{expected.inspect} for openid.#{key} "\
+                        "but got #{actual.inspect}: #{msg.inspect}")
+          assert_equal(expected, actual, error_text)
+        end
+
+        def assert_anonymous(msg)
+          ['claimed_id', 'identity'].each do |key|
+            assert_openid_key_absent(msg, key)
+          end
+        end
+
+        def assert_has_required_fields(msg)
+          internal_message = @checkid_req.instance_variable_get(:@message)
+          assert_equal(preferred_namespace,
+                       internal_message.get_openid_namespace)
+
+          assert_equal(preferred_namespace, msg.get_openid_namespace)
+          assert_openid_value_equal(msg, 'mode', expected_mode)
+
+          # Implement these in subclasses because they depend on
+          # protocol differences!
+          assert_has_realm(msg)
+          assert_identifiers_present(msg)
+        end
+
+        # TESTS
+
+        def test_check_no_assoc_handle
+          @checkid_req.instance_variable_set('@assoc', nil)
+          msg = assert_log_matches("Generated checkid") {
+            @checkid_req.get_message(@realm, @return_to, immediate)
+          }
+          assert_openid_key_absent(msg, 'assoc_handle')
+        end
+
+        def test_check_with_assoc_handle
+          msg = assert_log_matches("Generated checkid") {
+            @checkid_req.get_message(@realm, @return_to, immediate)
+          }
+
+          assert_openid_value_equal(msg, 'assoc_handle', @assoc.handle)
+        end
+
+        def test_add_extension_arg
+          @checkid_req.add_extension_arg('bag:', 'color', 'brown')
+          @checkid_req.add_extension_arg('bag:', 'material', 'paper')
+          assert(@checkid_req.message.namespaces.member?('bag:'))
+          assert_equal(@checkid_req.message.get_args('bag:'),
+                       {'color' => 'brown', 'material' => 'paper'})
+
+          msg = assert_log_matches("Generated checkid") {
+            @checkid_req.get_message(@realm, @return_to, immediate)
+          }
+
+          # XXX: this depends on the way that Message assigns
+          # namespaces. Really it doesn't care that it has alias "0",
+          # but that is tested anyway
+          post_args = msg.to_post_args()
+          assert_equal('brown', post_args['openid.ext0.color'])
+          assert_equal('paper', post_args['openid.ext0.material'])
+        end
+
+        def test_standard
+          msg = assert_log_matches('Generated checkid') {
+            @checkid_req.get_message(@realm, @return_to, immediate)
+          }
+          assert_has_identifiers(msg, @endpoint.local_id, @endpoint.claimed_id)
+        end
+
+        def test_send_redirect?
+          silence_logging {
+            url = @checkid_req.redirect_url(@realm, @return_to, immediate)
+            assert(url.length < OPENID1_URL_LIMIT)
+            assert(@checkid_req.send_redirect?(@realm, @return_to, immediate))
+
+            @return_to << '/foo' * 1000
+            url = @checkid_req.redirect_url(@realm, @return_to, immediate)
+            assert(url.length > OPENID1_URL_LIMIT)
+            actual = @checkid_req.send_redirect?(@realm, @return_to, immediate)
+            expected = preferred_namespace != OPENID2_NS
+            assert_equal(expected, actual)
+          }
+        end
+      end
+
+      class TestCheckIDRequestOpenID2 < Test::Unit::TestCase
+        include CheckIDTestMixin
+
+        def immediate
+          false
+        end
+
+        def expected_mode
+          'checkid_setup'
+        end
+
+        def preferred_namespace
+          OPENID2_NS
+        end
+
+        # check presence of proper realm key and absence of the wrong
+        # one.
+        def assert_has_realm(msg)
+          assert_openid_value_equal(msg, 'realm', @realm)
+          assert_openid_key_absent(msg, 'trust_root')
+        end
+
+        def assert_identifiers_present(msg)
+          identity_present = msg.has_key?(OPENID_NS, 'identity')
+          claimed_present = msg.has_key?(OPENID_NS, 'claimed_id')
+
+          assert_equal(claimed_present, identity_present)
+        end
+
+        # OpenID Checkid_Requests should be able to set 'anonymous' to true.
+        def test_set_anonymous_works_for_openid2
+          assert(@checkid_req.message.is_openid2)
+          assert_nothing_raised {@checkid_req.anonymous = true}
+          assert_nothing_raised {@checkid_req.anonymous = false}
+        end
+
+        def test_user_anonymous_ignores_identfier
+          @checkid_req.anonymous = true
+          msg = assert_log_matches('Generated checkid') {
+            @checkid_req.get_message(@realm, @return_to, immediate)
+          }
+          assert_has_required_fields(msg)
+          assert_anonymous(msg)
+        end
+
+        def test_op_anonymous_ignores_identifier
+          @endpoint.is_op_identifier = true
+          @checkid_req.anonymous = true
+          msg = assert_log_matches('Generated checkid') {
+            @checkid_req.get_message(@realm, @return_to, immediate)
+          }
+          assert_has_required_fields(msg)
+          assert_anonymous(msg)
+        end
+
+        def test_op_identifier_sends_identifier_select
+          @endpoint.is_op_identifier = true
+          msg = assert_log_matches('Generated checkid') {
+            @checkid_req.get_message(@realm, @return_to, immediate)
+          }
+          assert_has_required_fields(msg)
+          assert_has_identifiers(msg, IDENTIFIER_SELECT, IDENTIFIER_SELECT)
+        end
+      end
+
+      class TestCheckIDRequestOpenID1 < Test::Unit::TestCase
+        include CheckIDTestMixin
+
+        def immediate
+          false
+        end
+
+        def preferred_namespace
+          OPENID1_NS
+        end
+
+        def expected_mode
+          'checkid_setup'
+        end
+
+        # Make sure claimed_is is *absent* in request.
+        def assert_has_identifiers(msg, op_specific_id, claimed_id)
+          assert_openid_value_equal(msg, 'identity', op_specific_id)
+          assert_openid_key_absent(msg, 'claimed_id')
+        end
+
+        def assert_identifiers_present(msg)
+          assert_openid_key_absent(msg, 'claimed_id')
+          assert(msg.has_key?(OPENID_NS, 'identity'))
+        end
+
+        # check presence of proper realm key and absence of the wrong
+        # one.
+        def assert_has_realm(msg)
+          assert_openid_value_equal(msg, 'trust_root', @realm)
+          assert_openid_key_absent(msg, 'realm')
+        end
+
+        # TESTS
+
+        # OpenID 1 requests MUST NOT be able to set anonymous to true
+        def test_set_anonymous_fails_for_openid1
+          assert(@checkid_req.message.is_openid1)
+          assert_raises(ArgumentError) {
+            @checkid_req.anonymous = true
+          }
+          assert_nothing_raised{
+            @checkid_req.anonymous = false
+          }
+        end
+
+        # Identfier select SHOULD NOT be sent, but this pathway is in
+        # here in case some special discovery stuff is done to trigger
+        # it with OpenID 1. If it is triggered, it will send
+        # identifier_select just like OpenID 2.
+        def test_identifier_select
+          @endpoint.is_op_identifier = true
+          msg = assert_log_matches('Generated checkid') {
+            @checkid_req.get_message(@realm, @return_to, immediate)
+          }
+          assert_has_required_fields(msg)
+          assert_equal(IDENTIFIER_SELECT,
+                       msg.get_arg(OPENID1_NS, 'identity'))
+        end
+
+      end
+
+      class TestCheckIDRequestOpenID1Immediate < TestCheckIDRequestOpenID1
+        def immediate
+          true
+        end
+
+        def expected_mode
+          'checkid_immediate'
+        end
+      end
+
+      class TestCheckid_RequestOpenID2Immediate < TestCheckIDRequestOpenID2
+        def immediate
+          true
+        end
+
+        def expected_mode
+          'checkid_immediate'
+        end
+      end
+    end
+  end
+end
diff --git a/vendor/gems/ruby-openid-2.1.4/test/test_consumer.rb b/vendor/gems/ruby-openid-2.1.4/test/test_consumer.rb
new file mode 100644 (file)
index 0000000..dc4a09e
--- /dev/null
@@ -0,0 +1,257 @@
+require "openid/consumer"
+require "test/unit"
+require "testutil"
+
+module OpenID
+  class Consumer
+    module TestConsumer
+      class TestLastEndpoint < Test::Unit::TestCase
+        def test_set_get
+          session = {}
+          consumer = Consumer.new(session, nil)
+          consumer.send(:last_requested_endpoint=, :endpoint)
+          ep = consumer.send(:last_requested_endpoint)
+          assert_equal(:endpoint, ep)
+          ep = consumer.send(:last_requested_endpoint)
+          assert_equal(:endpoint, ep)
+          consumer.send(:cleanup_last_requested_endpoint)
+          ep = consumer.send(:last_requested_endpoint)
+          assert_equal(nil, ep)
+        end
+      end
+
+      class TestBegin < Test::Unit::TestCase
+        attr_accessor :user_input, :anonymous, :services,
+          :discovered_identifier, :checkid_request, :service
+
+        def setup
+          @discovered_identifier = 'http://discovered/'
+          @user_input = 'user.input'
+          @service = :service
+          @services = [@service]
+          @session = {}
+          @anonymous = false
+          @checkid_request = :checkid_request
+        end
+
+        def consumer
+          test = self
+          consumer = Consumer.new(@session, nil)
+          consumer.extend(InstanceDefExtension)
+          consumer.instance_def(:discover) do |identifier|
+            test.assert_equal(test.user_input, identifier)
+            [test.discovered_identifier, test.services]
+          end
+          consumer.instance_def(:begin_without_discovery) do
+            |service, sent_anonymous|
+            test.assert_equal(test.service, service)
+            test.assert_equal(test.anonymous, sent_anonymous)
+            test.checkid_request
+          end
+          consumer
+        end
+
+        def test_begin
+          checkid_request = consumer.begin(@user_input, @anonymous)
+          assert_equal(:checkid_request, checkid_request)
+          assert_equal(['OpenID::Consumer::DiscoveredServices::'\
+                        'OpenID::Consumer::'], @session.keys.sort!)
+        end
+
+        def test_begin_failure
+          @services = []
+          assert_raises(DiscoveryFailure) {
+            consumer.begin(@user_input, @anonymous)
+          }
+        end
+
+        def test_begin_fallback
+          @services = [:service1, :service2]
+          consumer = self.consumer
+          @service = :service1
+          consumer.begin(@user_input, @anonymous)
+          @service = :service2
+          consumer.begin(@user_input, @anonymous)
+          @service = :service1
+          consumer.begin(@user_input, @anonymous)
+          @service = :service2
+          consumer.begin(@user_input, @anonymous)
+        end
+      end
+
+      class TestBeginWithoutDiscovery < Test::Unit::TestCase
+        attr_reader :assoc
+        def setup
+          @session = {}
+          @assoc = :assoc
+          @service = OpenIDServiceEndpoint.new
+          @claimed_id = 'http://claimed.id/'
+          @service.claimed_id = @claimed_id
+          @anonymous = false
+        end
+
+        def consumer
+          test = self
+          assoc_manager = Object.new
+          assoc_manager.extend(InstanceDefExtension)
+          assoc_manager.instance_def(:get_association) do
+            test.assoc
+          end
+
+          consumer = Consumer.new(@session, nil)
+          consumer.extend(InstanceDefExtension)
+          consumer.instance_def(:association_manager) do |service|
+            assoc_manager
+          end
+          consumer
+        end
+
+        def call_begin_without_discovery
+          result = consumer.begin_without_discovery(@service, @anonymous)
+          assert(result.instance_of?(CheckIDRequest))
+          assert_equal(@anonymous, result.anonymous)
+          assert_equal(@service, consumer.send(:last_requested_endpoint))
+          assert_equal(result.instance_variable_get(:@assoc), @assoc)
+          return result
+        end
+
+        def cid_name
+          Consumer.openid1_return_to_claimed_id_name
+        end
+
+        def nonce_name
+          Consumer.openid1_return_to_nonce_name
+        end
+
+        def test_begin_without_openid1
+          result = call_begin_without_discovery
+
+          assert_equal(@claimed_id, result.return_to_args[cid_name])
+          assert_equal([cid_name, nonce_name].sort!,
+                       result.return_to_args.keys.sort!)
+        end
+
+        def test_begin_without_openid1_anonymous
+          @anonymous = true
+          assert_raises(ArgumentError) {
+            call_begin_without_discovery
+          }
+        end
+
+        def test_begin_without_openid2
+          @service.type_uris = [OPENID_2_0_TYPE]
+          result = call_begin_without_discovery
+
+          assert(result.return_to_args.empty?)
+        end
+
+        def test_begin_without_openid2_anonymous
+          @anonymous = true
+          @service.type_uris = [OPENID_2_0_TYPE]
+          result = call_begin_without_discovery
+
+          assert(result.return_to_args.empty?)
+        end
+      end
+
+      class TestComplete < Test::Unit::TestCase
+        def setup
+          @session = {}
+          @consumer = Consumer.new(@session, nil)
+        end
+
+        def test_bad_mode
+          response = @consumer.complete({'openid.ns' => OPENID2_NS,
+                                        'openid.mode' => 'bad'}, nil)
+          assert_equal(FAILURE, response.status)
+        end
+
+        def test_missing_mode
+          response = @consumer.complete({'openid.ns' => OPENID2_NS}, nil)
+          assert_equal(FAILURE, response.status)
+        end
+
+        def test_cancel
+          response = @consumer.complete({'openid.mode' => 'cancel'}, nil)
+          assert_equal(CANCEL, response.status)
+        end
+
+        def test_setup_needed_openid1
+          response = @consumer.complete({'openid.mode' => 'setup_needed'}, nil)
+          assert_equal(FAILURE, response.status)
+        end
+
+        def test_setup_needed_openid2
+          args = {'openid.ns' => OPENID2_NS, 'openid.mode' => 'setup_needed'}
+          response = @consumer.complete(args, nil)
+          assert_equal(SETUP_NEEDED, response.status)
+        end
+
+        def test_idres_setup_needed_openid1
+          setup_url = 'http://setup.url/'
+          args = {
+            'openid.user_setup_url' => setup_url,
+            'openid.mode' => 'id_res',
+          }
+          response = @consumer.complete(args, nil)
+          assert_equal(SETUP_NEEDED, response.status)
+        end
+
+        def test_error
+          contact = 'me'
+          reference = 'thing thing'
+          args = {
+            'openid.mode' => 'error',
+            'openid.contact' => contact,
+            'openid.reference' => reference,
+          }
+          response = @consumer.complete(args, nil)
+          assert_equal(FAILURE, response.status)
+          assert_equal(contact, response.contact)
+          assert_equal(reference, response.reference)
+
+          args['openid.ns'] = OPENID2_NS
+          response = @consumer.complete(args, nil)
+          assert_equal(FAILURE, response.status)
+          assert_equal(contact, response.contact)
+          assert_equal(reference, response.reference)
+        end
+
+        def test_idres_openid1
+          args = {
+            'openid.mode' => 'id_res',
+          }
+
+          endpoint = OpenIDServiceEndpoint.new
+          endpoint.claimed_id = :test_claimed_id
+
+          idres = Object.new
+          idres.extend(InstanceDefExtension)
+          idres.instance_def(:endpoint){endpoint}
+          idres.instance_def(:signed_fields){:test_signed_fields}
+
+          test = self
+          @consumer.extend(InstanceDefExtension)
+          @consumer.instance_def(:handle_idres) {|message, return_to|
+            test.assert_equal(args, message.to_post_args)
+            test.assert_equal(:test_return_to, return_to)
+            idres
+          }
+
+          response = @consumer.complete(args, :test_return_to)
+          assert_equal(SUCCESS, response.status, response.message)
+          assert_equal(:test_claimed_id, response.identity_url)
+          assert_equal(endpoint, response.endpoint)
+
+          error_message = "In Soviet Russia, id_res handles you!"
+          @consumer.instance_def(:handle_idres) {|message, return_to|
+            raise ProtocolError, error_message
+          }
+          response = @consumer.complete(args, :test_return_to)
+          assert_equal(FAILURE, response.status)
+          assert_equal(error_message, response.message)
+        end
+      end
+    end
+  end
+end
diff --git a/vendor/gems/ruby-openid-2.1.4/test/test_cryptutil.rb b/vendor/gems/ruby-openid-2.1.4/test/test_cryptutil.rb
new file mode 100644 (file)
index 0000000..f6a38a0
--- /dev/null
@@ -0,0 +1,119 @@
+# coding: ASCII-8BIT
+require 'test/unit'
+require "openid/cryptutil"
+require "pathname"
+
+class CryptUtilTestCase < Test::Unit::TestCase
+  BIG = 2 ** 256
+
+  def test_rand
+    # If this is not true, the rest of our test won't work
+    assert(BIG.is_a?(Bignum))
+
+    # It's possible that these will be small enough for fixnums, but
+    # extraorindarily unlikely.
+    a = OpenID::CryptUtil.rand(BIG)
+    b = OpenID::CryptUtil.rand(BIG)
+    assert(a.is_a?(Bignum))
+    assert(b.is_a?(Bignum))
+    assert_not_equal(a, b)
+  end
+
+  def test_rand_doesnt_depend_on_srand
+    Kernel.srand(1)
+    a = OpenID::CryptUtil.rand(BIG)
+    Kernel.srand(1)
+    b = OpenID::CryptUtil.rand(BIG)
+    assert_not_equal(a, b)
+  end
+
+  def test_random_binary_convert
+    (0..500).each do
+      n = (0..10).inject(0) {|sum, element| sum + OpenID::CryptUtil.rand(BIG) }
+      s = OpenID::CryptUtil.num_to_binary n
+      assert(s.is_a?(String))
+      n_converted_back = OpenID::CryptUtil.binary_to_num(s)
+      assert_equal(n, n_converted_back)
+    end
+  end
+
+  def test_enumerated_binary_convert
+    {
+        "\x00" => 0,
+        "\x01" => 1,
+        "\x7F" => 127,
+        "\x00\xFF" => 255,
+        "\x00\x80" => 128,
+        "\x00\x81" => 129,
+        "\x00\x80\x00" => 32768,
+        "OpenID is cool" => 1611215304203901150134421257416556,
+    }.each do |str, num|
+      num_prime = OpenID::CryptUtil.binary_to_num(str)
+      str_prime = OpenID::CryptUtil.num_to_binary(num)
+      assert_equal(num, num_prime)
+      assert_equal(str, str_prime)
+    end
+  end
+
+  def with_n2b64
+    test_dir = Pathname.new(__FILE__).dirname
+    filename = test_dir.join('data', 'n2b64')
+    File.open(filename) do |file|
+      file.each_line do |line|
+        base64, base10 = line.chomp.split
+        yield base64, base10.to_i
+      end
+    end
+  end
+
+  def test_base64_to_num
+    with_n2b64 do |base64, num|
+      assert_equal(num, OpenID::CryptUtil.base64_to_num(base64))
+    end
+  end
+
+  def test_base64_to_num_invalid
+    assert_raises(ArgumentError) {
+      OpenID::CryptUtil.base64_to_num('!@#$')
+    }
+  end
+
+  def test_num_to_base64
+    with_n2b64 do |base64, num|
+      assert_equal(base64, OpenID::CryptUtil.num_to_base64(num))
+    end
+  end
+
+  def test_randomstring
+    s1 = OpenID::CryptUtil.random_string(42)
+    assert_equal(42, s1.length)
+    s2 = OpenID::CryptUtil.random_string(42)
+    assert_equal(42, s2.length)
+    assert_not_equal(s1, s2)
+  end
+
+  def test_randomstring_population
+    s1 = OpenID::CryptUtil.random_string(42, "XO")
+    assert_match(/[XO]{42}/, s1)
+  end
+
+  def test_sha1
+    assert_equal("\x11\xf6\xad\x8e\xc5*)\x84\xab\xaa\xfd|;Qe\x03x\\ r",
+                 OpenID::CryptUtil.sha1('x'))
+  end
+
+  def test_hmac_sha1
+    assert_equal("\x8bo\xf7O\xa7\x18*\x90\xac ah\x16\xf7\xb8\x81JB\x9f|",
+                 OpenID::CryptUtil.hmac_sha1('x', 'x'))
+  end
+
+  def test_sha256
+    assert_equal("-q\x16B\xb7&\xb0D\x01b|\xa9\xfb\xac2\xf5\xc8S\x0f\xb1\x90<\xc4\xdb\x02%\x87\x17\x92\x1aH\x81",
+                 OpenID::CryptUtil.sha256('x'))
+  end
+
+  def test_hmac_sha256
+    assert_equal("\x94{\xd2w\xb2\xd3\\\xfc\x07\xfb\xc7\xe3b\xf2iuXz1\xf8:}\xffx\x8f\xda\xc1\xfaC\xc4\xb2\x87",
+                 OpenID::CryptUtil.hmac_sha256('x', 'x'))
+  end
+end
diff --git a/vendor/gems/ruby-openid-2.1.4/test/test_dh.rb b/vendor/gems/ruby-openid-2.1.4/test/test_dh.rb
new file mode 100644 (file)
index 0000000..397c15e
--- /dev/null
@@ -0,0 +1,86 @@
+require 'test/unit'
+require 'openid/dh'
+require 'testutil'
+
+module OpenID
+  class DiffieHellmanExposed < OpenID::DiffieHellman
+    def DiffieHellmanExposed.strxor_for_testing(a, b)
+      return DiffieHellmanExposed.strxor(a, b)
+    end
+  end
+
+  class DiffieHellmanTestCase < Test::Unit::TestCase
+    include OpenID::TestDataMixin
+
+    NUL = "\x00"
+
+    def test_strxor_success
+      [#input 1   input 2   expected
+       [NUL,      NUL,      NUL     ],
+       ["\x01",   NUL,      "\x01"  ],
+       ["a",      "a",      NUL     ],
+       ["a",      NUL,      "a"     ],
+       ["abc",    NUL * 3,  "abc"   ],
+       ["x" * 10, NUL * 10, "x" * 10],
+       ["\x01",   "\x02",   "\x03"  ],
+       ["\xf0",   "\x0f",   "\xff"  ],
+       ["\xff",   "\x0f",   "\xf0"  ],
+      ].each do |input1, input2, expected|
+        actual = DiffieHellmanExposed.strxor_for_testing(input1, input2)
+        assert_equal(expected, actual)
+      end
+    end
+
+    def test_strxor_failure
+      [
+       ['',      'a'    ],
+       ['foo',   'ba'   ],
+       [NUL * 3, NUL * 4],
+       [255,     127    ].map{|h| (0..h).map{|i|i.chr}.join('')},
+      ].each do |aa, bb|
+        assert_raises(ArgumentError) {
+          DiffieHellmanExposed.strxor(aa, bb)
+        }
+      end
+    end
+
+    def test_simple_exchange
+      dh1 = DiffieHellman.from_defaults()
+      dh2 = DiffieHellman.from_defaults()
+      secret1 = dh1.get_shared_secret(dh2.public)
+      secret2 = dh2.get_shared_secret(dh1.public)
+      assert_equal(secret1, secret2)
+    end
+
+    def test_xor_secret
+      dh1 = DiffieHellman.from_defaults()
+      dh2 = DiffieHellman.from_defaults()
+      secret = "Shhhhhh! don't tell!"
+      encrypted = dh1.xor_secret((CryptUtil.method :sha1), dh2.public, secret)
+      decrypted = dh2.xor_secret((CryptUtil.method :sha1), dh1.public, encrypted)
+      assert_equal(secret, decrypted)
+    end
+
+    def test_dh
+      dh = DiffieHellman.from_defaults()
+      class << dh
+        def set_private_test(priv)
+          set_private(priv)
+        end
+      end
+
+      read_data_file('dh.txt', true).each do |line|
+        priv, pub = line.split(' ').map {|x| x.to_i}
+        dh.set_private_test(priv)
+        assert_equal(dh.public, pub)
+      end
+    end
+
+    def test_using_defaults
+      dh = DiffieHellman.from_defaults()
+      assert(dh.using_default_values?)
+      dh = DiffieHellman.new(3, 2750161)
+      assert(!dh.using_default_values?)
+    end
+  end
+end
diff --git a/vendor/gems/ruby-openid-2.1.4/test/test_discover.rb b/vendor/gems/ruby-openid-2.1.4/test/test_discover.rb
new file mode 100644 (file)
index 0000000..13b115c
--- /dev/null
@@ -0,0 +1,838 @@
+
+require 'testutil'
+require 'util'
+
+require 'test/unit'
+require 'openid/fetchers'
+require 'openid/yadis/discovery'
+require 'openid/consumer/discovery'
+require 'openid/yadis/xrires'
+require 'openid/yadis/xri'
+require 'openid/message'
+require 'openid/util'
+
+### Tests for conditions that trigger DiscoveryFailure
+
+module OpenID
+  class SimpleMockFetcher
+    def initialize(test, responses)
+      @test = test
+      @responses = responses.dup
+    end
+
+    def fetch(url, body=nil, headers=nil, limit=nil)
+      response = @responses.shift
+      @test.assert(body.nil?)
+      @test.assert_equal(response.final_url, url)
+      return response
+    end
+  end
+
+  class TestDiscoveryFailure < Test::Unit::TestCase
+    def initialize(*args)
+      super(*args)
+
+      @responses = [
+                    [HTTPResponse._from_raw_data(nil, nil, {}, 'http://network.error/')],
+                    [HTTPResponse._from_raw_data(404, nil, {}, 'http://not.found/')],
+                    [HTTPResponse._from_raw_data(400, nil, {}, 'http://bad.request/')],
+                    [HTTPResponse._from_raw_data(500, nil, {}, 'http://server.error/')],
+                    [HTTPResponse._from_raw_data(200, nil, {'x-xrds-location' => 'http://xrds.missing/'},
+                                                 'http://header.found/'),
+                     HTTPResponse._from_raw_data(404, nil, {}, 'http://xrds.missing/')],
+                    ]
+    end
+
+    def test_discovery_failure
+
+      @responses.each { |response_set|
+        @url = response_set[0].final_url
+        OpenID.fetcher = SimpleMockFetcher.new(self, response_set)
+      
+        expected_status = response_set[-1].code
+        begin
+          OpenID.discover(@url)
+        rescue DiscoveryFailure => why
+          assert_equal(why.http_response.code, expected_status)
+        else
+          flunk('Did not raise DiscoveryFailure')
+        end
+
+        OpenID.fetcher = nil
+      }
+    end
+  end
+
+  ### Tests for raising/catching exceptions from the fetcher through
+  ### the discover function
+
+  class ErrorRaisingFetcher
+    # Just raise an exception when fetch is called
+
+    def initialize(thing_to_raise)
+      @thing_to_raise = thing_to_raise
+    end
+
+    def fetch(url, body=nil, headers=nil, limit=nil)
+      raise @thing_to_raise
+    end
+  end
+
+  class DidFetch < Exception
+    # Custom exception just to make sure it's not handled differently
+  end
+
+  class TestFetchException < Test::Unit::TestCase
+    # Discovery should only raise DiscoveryFailure
+
+    def initialize(*args)
+      super(*args)
+
+      @cases = [
+                DidFetch.new(),
+                Exception.new(),
+                ArgumentError.new(),
+                RuntimeError.new(),
+               ]
+    end
+
+    def test_fetch_exception
+      @cases.each { |exc|
+        OpenID.fetcher = ErrorRaisingFetcher.new(exc)
+        assert_raises(DiscoveryFailure) {
+          OpenID.discover('http://doesnt.matter/')
+        }
+        OpenID.fetcher = nil
+      }
+    end
+  end
+
+  ### Tests for openid.consumer.discover.discover
+
+  class TestNormalization < Test::Unit::TestCase
+    def test_addingProtocol
+      f = ErrorRaisingFetcher.new(RuntimeError.new())
+      OpenID.fetcher = f
+
+      begin
+        OpenID.discover('users.stompy.janrain.com:8000/x')
+      rescue DiscoveryFailure => why
+        assert why.to_s.match("Failed to fetch")
+      rescue RuntimeError
+      end
+
+      OpenID.fetcher = nil
+    end
+  end
+
+  class DiscoveryMockFetcher
+    def initialize(documents)
+      @redirect = nil
+      @documents = documents
+      @fetchlog = []
+    end
+
+    def fetch(url, body=nil, headers=nil, limit=nil)
+      @fetchlog << [url, body, headers]
+      if @redirect
+        final_url = @redirect
+      else
+        final_url = url
+      end
+
+      begin
+        ctype, body = @documents.fetch(url)
+      rescue IndexError
+        status = 404
+        ctype = 'text/plain'
+        body = ''
+      else
+        status = 200
+      end
+
+      return HTTPResponse._from_raw_data(status, body, {'content-type' => ctype}, final_url)
+    end
+  end
+
+  class BaseTestDiscovery < Test::Unit::TestCase
+    attr_accessor :id_url, :fetcher_class
+
+    def initialize(*args)
+      super(*args)
+      @id_url = "http://someuser.unittest/"
+      @documents = {}
+      @fetcher_class = DiscoveryMockFetcher
+    end
+
+    def _checkService(s, server_url, claimed_id=nil,
+                      local_id=nil, canonical_id=nil,
+                      types=nil, used_yadis=false,
+                      display_identifier=nil)
+      assert_equal(server_url, s.server_url)
+      if types == ['2.0 OP']
+        assert(!claimed_id)
+        assert(!local_id)
+        assert(!s.claimed_id)
+        assert(!s.local_id)
+        assert(!s.get_local_id())
+        assert(!s.compatibility_mode())
+        assert(s.is_op_identifier())
+        assert_equal(s.preferred_namespace(),
+                     OPENID_2_0_MESSAGE_NS)
+      else
+        assert_equal(claimed_id, s.claimed_id)
+        assert_equal(local_id, s.get_local_id())
+      end
+
+      if used_yadis
+        assert(s.used_yadis, "Expected to use Yadis")
+      else
+        assert(!s.used_yadis,
+               "Expected to use old-style discovery")
+      end
+
+      openid_types = {
+        '1.1' => OPENID_1_1_TYPE,
+        '1.0' => OPENID_1_0_TYPE,
+        '2.0' => OPENID_2_0_TYPE,
+        '2.0 OP' => OPENID_IDP_2_0_TYPE,
+      }
+
+      type_uris = types.collect { |t| openid_types[t] }
+
+      assert_equal(type_uris, s.type_uris)
+      assert_equal(canonical_id, s.canonical_id)
+
+      if canonical_id.nil?
+        assert_equal(claimed_id, s.display_identifier)
+      else
+        assert_equal(display_identifier, s.display_identifier)
+      end
+    end
+
+    def setup
+      # @documents = @documents.dup
+      @fetcher = @fetcher_class.new(@documents)
+      OpenID.fetcher = @fetcher
+    end
+
+    def teardown
+      OpenID.fetcher = nil
+    end
+
+    def test_blank
+      # XXX to avoid > 0 test requirement
+    end
+  end
+
+#   def readDataFile(filename):
+#     module_directory = os.path.dirname(os.path.abspath(__file__))
+#     filename = os.path.join(
+#         module_directory, 'data', 'test_discover', filename)
+#     return file(filename).read()
+
+  class TestDiscovery < BaseTestDiscovery
+    include TestDataMixin
+
+    def _discover(content_type, data,
+                  expected_services, expected_id=nil)
+      if expected_id.nil?
+        expected_id = @id_url
+      end
+
+      @documents[@id_url] = [content_type, data]
+      id_url, services = OpenID.discover(@id_url)
+
+      assert_equal(expected_services, services.length)
+      assert_equal(expected_id, id_url)
+      return services
+    end
+
+    def test_404
+      assert_raise(DiscoveryFailure) {
+        OpenID.discover(@id_url + '/404')
+      }
+    end
+
+    def test_noOpenID
+      services = _discover('text/plain',
+                           "junk", 0)
+
+      services = _discover(
+                           'text/html',
+                           read_data_file('test_discover/openid_no_delegate.html', false),
+                           1)
+
+      _checkService(
+                    services[0],
+                    "http://www.myopenid.com/server",
+                    @id_url,
+                    @id_url,
+                    nil,
+                    ['1.1'],
+                    false)
+    end
+
+    def test_malformed_meta_tag
+      @id_url = "http://user.myopenid.com/"
+
+      services = _discover(
+                           'text/html',
+                           read_data_file('test_discover/malformed_meta_tag.html', false),
+                           2)
+
+      _checkService(
+                    services[0],
+                    "http://www.myopenid.com/server",
+                    @id_url,
+                    @id_url,
+                    nil,
+                    ['2.0'],
+                    false)
+
+      _checkService(
+                    services[1],
+                    "http://www.myopenid.com/server",
+                    @id_url,
+                    @id_url,
+                    nil,
+                    ['1.1'],
+                    false)
+    end
+
+    def test_html1
+      services = _discover('text/html',
+                           read_data_file('test_discover/openid.html', false),
+                           1)
+
+      _checkService(services[0],
+                    "http://www.myopenid.com/server",
+                    @id_url,
+                    'http://smoker.myopenid.com/',
+                    nil,
+                    ['1.1'],
+                    false)
+    end
+
+    def test_html1Fragment
+      # Ensure that the Claimed Identifier does not have a fragment if
+      # one is supplied in the User Input.
+      content_type = 'text/html'
+      data = read_data_file('test_discover/openid.html', false)
+      expected_services = 1
+
+      @documents[@id_url] = [content_type, data]
+      expected_id = @id_url
+      @id_url = @id_url + '#fragment'
+      id_url, services = OpenID.discover(@id_url)
+
+      assert_equal(expected_services, services.length)
+      assert_equal(expected_id, id_url)
+
+      _checkService(services[0],
+                    "http://www.myopenid.com/server",
+                    expected_id,
+                    'http://smoker.myopenid.com/',
+                    nil,
+                    ['1.1'],
+                    false)
+    end
+
+    def test_html2
+      services = _discover('text/html',
+                           read_data_file('test_discover/openid2.html', false),
+                           1)
+
+      _checkService(services[0],
+                    "http://www.myopenid.com/server",
+                    @id_url,
+                    'http://smoker.myopenid.com/',
+                    nil,
+                    ['2.0'],
+                    false)
+    end
+
+    def test_html1And2
+      services = _discover(
+                           'text/html',
+                           read_data_file('test_discover/openid_1_and_2.html', false),
+                           2)
+
+      services.zip(['2.0', '1.1']).each { |s, t|
+          _checkService(s,
+                        "http://www.myopenid.com/server",
+                        @id_url,
+                        'http://smoker.myopenid.com/',
+                        nil,
+                        [t],
+                        false)
+      }
+    end
+
+    def test_yadisEmpty
+      services = _discover('application/xrds+xml',
+                           read_data_file('test_discover/yadis_0entries.xml', false),
+                           0)
+    end
+
+    def test_htmlEmptyYadis
+      # HTML document has discovery information, but points to an
+      # empty Yadis document.  The XRDS document pointed to by
+      # "openid_and_yadis.html"
+      @documents[@id_url + 'xrds'] = ['application/xrds+xml',
+                                      read_data_file('test_discover/yadis_0entries.xml', false)]
+
+      services = _discover('text/html',
+                           read_data_file('test_discover/openid_and_yadis.html', false),
+                           1)
+
+      _checkService(services[0],
+                    "http://www.myopenid.com/server",
+                    @id_url,
+                    'http://smoker.myopenid.com/',
+                    nil,
+                    ['1.1'],
+                    false)
+    end
+
+    def test_yadis1NoDelegate
+      services = _discover('application/xrds+xml',
+                           read_data_file('test_discover/yadis_no_delegate.xml', false),
+                           1)
+
+      _checkService(services[0],
+                    "http://www.myopenid.com/server",
+                    @id_url,
+                    @id_url,
+                    nil,
+                    ['1.0'],
+                    true)
+    end
+
+    def test_yadis2NoLocalID
+      services = _discover('application/xrds+xml',
+                           read_data_file('test_discover/openid2_xrds_no_local_id.xml', false),
+                           1)
+
+      _checkService(services[0],
+                    "http://www.myopenid.com/server",
+                    @id_url,
+                    @id_url,
+                    nil,
+                    ['2.0'],
+                    true)
+    end
+
+    def test_yadis2
+      services = _discover('application/xrds+xml',
+                           read_data_file('test_discover/openid2_xrds.xml', false),
+                           1)
+
+      _checkService(services[0],
+                    "http://www.myopenid.com/server",
+                    @id_url,
+                    'http://smoker.myopenid.com/',
+                    nil,
+                    ['2.0'],
+                    true)
+    end
+
+    def test_yadis2OP
+      services = _discover('application/xrds+xml',
+                           read_data_file('test_discover/yadis_idp.xml', false),
+                           1)
+
+      _checkService(services[0],
+                    "http://www.myopenid.com/server",
+                    nil, nil, nil,
+                    ['2.0 OP'],
+                    true)
+    end
+
+    def test_yadis2OPDelegate
+      # The delegate tag isn't meaningful for OP entries.
+      services = _discover('application/xrds+xml',
+                           read_data_file('test_discover/yadis_idp_delegate.xml', false),
+                           1)
+
+      _checkService(services[0],
+                    "http://www.myopenid.com/server",
+                    nil, nil, nil,
+                    ['2.0 OP'],
+                    true)
+    end
+
+    def test_yadis2BadLocalID
+      assert_raise(DiscoveryFailure) {
+        _discover('application/xrds+xml',
+                  read_data_file('test_discover/yadis_2_bad_local_id.xml', false),
+                  1)
+      }
+    end
+
+    def test_yadis1And2
+      services = _discover('application/xrds+xml',
+                           read_data_file('test_discover/openid_1_and_2_xrds.xml', false),
+                           1)
+
+      _checkService(services[0],
+                    "http://www.myopenid.com/server",
+                    @id_url,
+                    'http://smoker.myopenid.com/',
+                    nil,
+                    ['2.0', '1.1'],
+                    true)
+    end
+
+    def test_yadis1And2BadLocalID
+      assert_raise(DiscoveryFailure) {
+        _discover('application/xrds+xml',
+                  read_data_file('test_discover/openid_1_and_2_xrds_bad_delegate.xml', false),
+                  1)
+      }
+    end
+  end
+
+  class MockFetcherForXRIProxy
+
+    def initialize(documents, proxy_url=Yadis::XRI::ProxyResolver::DEFAULT_PROXY)
+      @documents = documents
+      @fetchlog = []
+      @proxy_url = nil
+    end
+
+    def fetch(url, body=nil, headers=nil, limit=nil)
+      @fetchlog << [url, body, headers]
+
+      u = URI::parse(url)
+      proxy_host = u.host
+      xri = u.path
+      query = u.query
+
+      if !headers and !query
+        raise ArgumentError.new("No headers or query; you probably didn't " +
+                                "mean to do that.")
+      end
+
+      if xri.starts_with?('/')
+        xri = xri[1..-1]
+      end
+
+      begin
+        ctype, body = @documents.fetch(xri)
+      rescue IndexError
+        status = 404
+        ctype = 'text/plain'
+        body = ''
+      else
+        status = 200
+      end
+
+      return HTTPResponse._from_raw_data(status, body,
+                                         {'content-type' => ctype}, url)
+    end
+  end
+
+  class TestXRIDiscovery < BaseTestDiscovery
+
+    include TestDataMixin
+    include TestUtil
+
+    def initialize(*args)
+      super(*args)
+
+      @fetcher_class = MockFetcherForXRIProxy
+
+      @documents = {'=smoker' => ['application/xrds+xml',
+                                  read_data_file('test_discover/yadis_2entries_delegate.xml', false)],
+        '=smoker*bad' => ['application/xrds+xml',
+                          read_data_file('test_discover/yadis_another_delegate.xml', false)]}
+    end
+
+    def test_xri
+      user_xri, services = OpenID.discover_xri('=smoker')
+
+      _checkService(services[0],
+                    "http://www.myopenid.com/server",
+                    Yadis::XRI.make_xri("=!1000"),
+                    'http://smoker.myopenid.com/',
+                    Yadis::XRI.make_xri("=!1000"),
+                    ['1.0'],
+                    true,
+                    '=smoker')
+
+      _checkService(services[1],
+                    "http://www.livejournal.com/openid/server.bml",
+                    Yadis::XRI.make_xri("=!1000"),
+                    'http://frank.livejournal.com/',
+                    Yadis::XRI.make_xri("=!1000"),
+                    ['1.0'],
+                    true,
+                    '=smoker')
+    end
+
+    def test_xri_normalize
+      user_xri, services = OpenID.discover_xri('xri://=smoker')
+
+      _checkService(services[0],
+                    "http://www.myopenid.com/server",
+                    Yadis::XRI.make_xri("=!1000"),
+                    'http://smoker.myopenid.com/',
+                    Yadis::XRI.make_xri("=!1000"),
+                    ['1.0'],
+                    true,
+                    '=smoker')
+
+      _checkService(services[1],
+                    "http://www.livejournal.com/openid/server.bml",
+                    Yadis::XRI.make_xri("=!1000"),
+                    'http://frank.livejournal.com/',
+                    Yadis::XRI.make_xri("=!1000"),
+                    ['1.0'],
+                    true,
+                    '=smoker')
+    end
+
+    def test_xriNoCanonicalID
+      silence_logging {
+        user_xri, services = OpenID.discover_xri('=smoker*bad')
+        assert(services.empty?)
+      }
+    end
+
+    def test_useCanonicalID
+      # When there is no delegate, the CanonicalID should be used with
+      # XRI.
+      endpoint = OpenIDServiceEndpoint.new()
+      endpoint.claimed_id = Yadis::XRI.make_xri("=!1000")
+      endpoint.canonical_id = Yadis::XRI.make_xri("=!1000")
+      assert_equal(endpoint.get_local_id, Yadis::XRI.make_xri("=!1000"))
+    end
+  end
+
+  class TestXRIDiscoveryIDP < BaseTestDiscovery
+    include TestDataMixin
+
+    def initialize(*args)
+      super(*args)
+
+      @fetcher_class = MockFetcherForXRIProxy
+
+      @documents = {'=smoker' => ['application/xrds+xml',
+                                  read_data_file('test_discover/yadis_2entries_idp.xml', false)] }
+    end
+
+    def test_xri
+      user_xri, services = OpenID.discover_xri('=smoker')
+      assert(!services.empty?, "Expected services, got zero")
+      assert_equal(services[0].server_url,
+                   "http://www.livejournal.com/openid/server.bml")
+    end
+  end
+
+  class TestPreferredNamespace < Test::Unit::TestCase
+    def initialize(*args)
+      super(*args)
+
+      @cases = [
+               [OPENID1_NS, []],
+               [OPENID1_NS, ['http://jyte.com/']],
+               [OPENID1_NS, [OPENID_1_0_TYPE]],
+               [OPENID1_NS, [OPENID_1_1_TYPE]],
+               [OPENID2_NS, [OPENID_2_0_TYPE]],
+               [OPENID2_NS, [OPENID_IDP_2_0_TYPE]],
+               [OPENID2_NS, [OPENID_2_0_TYPE,
+                             OPENID_1_0_TYPE]],
+               [OPENID2_NS, [OPENID_1_0_TYPE,
+                             OPENID_2_0_TYPE]],
+              ]
+    end
+
+    def test_preferred_namespace
+
+      @cases.each { |expected_ns, type_uris|
+        endpoint = OpenIDServiceEndpoint.new()
+        endpoint.type_uris = type_uris
+        actual_ns = endpoint.preferred_namespace()
+        assert_equal(actual_ns, expected_ns)
+      }
+    end
+  end
+
+  class TestIsOPIdentifier < Test::Unit::TestCase
+    def setup
+      @endpoint = OpenIDServiceEndpoint.new()
+    end
+
+    def test_none
+      assert(!@endpoint.is_op_identifier())
+    end
+
+    def test_openid1_0
+      @endpoint.type_uris = [OPENID_1_0_TYPE]
+      assert(!@endpoint.is_op_identifier())
+    end
+
+    def test_openid1_1
+      @endpoint.type_uris = [OPENID_1_1_TYPE]
+      assert(!@endpoint.is_op_identifier())
+    end
+
+    def test_openid2
+      @endpoint.type_uris = [OPENID_2_0_TYPE]
+      assert(!@endpoint.is_op_identifier())
+    end
+
+    def test_openid2OP
+      @endpoint.type_uris = [OPENID_IDP_2_0_TYPE]
+      assert(@endpoint.is_op_identifier())
+    end
+
+    def test_multipleMissing
+      @endpoint.type_uris = [OPENID_2_0_TYPE,
+                             OPENID_1_0_TYPE]
+      assert(!@endpoint.is_op_identifier())
+    end
+
+    def test_multiplePresent
+      @endpoint.type_uris = [OPENID_2_0_TYPE,
+                             OPENID_1_0_TYPE,
+                             OPENID_IDP_2_0_TYPE]
+      assert(@endpoint.is_op_identifier())
+    end
+  end
+
+  class TestFromOPEndpointURL < Test::Unit::TestCase
+    def setup
+      @op_endpoint_url = 'http://example.com/op/endpoint'
+      @endpoint = OpenIDServiceEndpoint.from_op_endpoint_url(@op_endpoint_url)
+    end
+
+    def test_isOPEndpoint
+      assert(@endpoint.is_op_identifier())
+    end
+
+    def test_noIdentifiers
+      assert_equal(@endpoint.get_local_id, nil)
+      assert_equal(@endpoint.claimed_id, nil)
+    end
+
+    def test_compatibility
+      assert(!@endpoint.compatibility_mode())
+    end
+
+    def test_canonical_id
+      assert_equal(@endpoint.canonical_id, nil)
+    end
+
+    def test_serverURL
+      assert_equal(@endpoint.server_url, @op_endpoint_url)
+    end
+  end
+
+  class TestDiscoverFunction < Test::Unit::TestCase
+    def test_discover_function
+      # XXX these were all different tests in python, but they're
+      # combined here so I only have to use with_method_overridden
+      # once.
+      discoverXRI = Proc.new { |identifier|
+        return 'XRI'
+      }
+
+      discoverURI = Proc.new { |identifier|
+        return 'URI'
+      }
+
+      OpenID.extend(OverrideMethodMixin)
+
+      OpenID.with_method_overridden(:discover_uri, discoverURI) do
+        OpenID.with_method_overridden(:discover_xri, discoverXRI) do
+          assert_equal('URI', OpenID.discover('http://woo!'))
+          assert_equal('URI', OpenID.discover('not a URL or XRI'))
+          assert_equal('XRI', OpenID.discover('xri://=something'))
+          assert_equal('XRI', OpenID.discover('=something'))
+        end
+      end
+    end
+  end
+
+  class TestEndpointSupportsType < Test::Unit::TestCase
+    def setup
+      @endpoint = OpenIDServiceEndpoint.new()
+    end
+
+    def failUnlessSupportsOnly(*types)
+      ['foo',
+       OPENID_1_1_TYPE,
+       OPENID_1_0_TYPE,
+       OPENID_2_0_TYPE,
+       OPENID_IDP_2_0_TYPE].each { |t|
+        if types.member?(t)
+          assert(@endpoint.supports_type(t),
+                 sprintf("Must support %s", t))
+        else
+          assert(!@endpoint.supports_type(t),
+                 sprintf("Shouldn't support %s", t))
+        end
+      }
+    end
+
+    def test_supportsNothing
+      failUnlessSupportsOnly()
+    end
+
+    def test_openid2
+      @endpoint.type_uris = [OPENID_2_0_TYPE]
+      failUnlessSupportsOnly(OPENID_2_0_TYPE)
+    end
+
+    def test_openid2provider
+      @endpoint.type_uris = [OPENID_IDP_2_0_TYPE]
+      failUnlessSupportsOnly(OPENID_IDP_2_0_TYPE,
+                             OPENID_2_0_TYPE)
+    end
+
+    def test_openid1_0
+      @endpoint.type_uris = [OPENID_1_0_TYPE]
+      failUnlessSupportsOnly(OPENID_1_0_TYPE)
+    end
+
+    def test_openid1_1
+      @endpoint.type_uris = [OPENID_1_1_TYPE]
+      failUnlessSupportsOnly(OPENID_1_1_TYPE)
+    end
+
+    def test_multiple
+      @endpoint.type_uris = [OPENID_1_1_TYPE,
+                             OPENID_2_0_TYPE]
+      failUnlessSupportsOnly(OPENID_1_1_TYPE,
+                             OPENID_2_0_TYPE)
+    end
+
+    def test_multipleWithProvider
+      @endpoint.type_uris = [OPENID_1_1_TYPE,
+                             OPENID_2_0_TYPE,
+                             OPENID_IDP_2_0_TYPE]
+      failUnlessSupportsOnly(OPENID_1_1_TYPE,
+                             OPENID_2_0_TYPE,
+                             OPENID_IDP_2_0_TYPE)
+    end
+  end
+
+  class TestEndpointDisplayIdentifier < Test::Unit::TestCase
+    def test_strip_fragment
+      @endpoint = OpenIDServiceEndpoint.new()
+      @endpoint.claimed_id = 'http://recycled.invalid/#123'
+      assert_equal 'http://recycled.invalid/', @endpoint.display_identifier
+    end
+  end
+
+
+  class TestNormalizeURL < Test::Unit::TestCase
+    def test_no_host
+      assert_raise(DiscoveryFailure) {
+        OpenID::normalize_url('http:///too-many.invalid/slashes')
+      }
+    end
+  end
+end
diff --git a/vendor/gems/ruby-openid-2.1.4/test/test_discovery_manager.rb b/vendor/gems/ruby-openid-2.1.4/test/test_discovery_manager.rb
new file mode 100644 (file)
index 0000000..da966f3
--- /dev/null
@@ -0,0 +1,262 @@
+
+require 'test/unit'
+require 'openid/consumer/discovery_manager'
+require 'openid/extras'
+
+require 'testutil'
+
+module OpenID
+  class TestDiscoveredServices < Test::Unit::TestCase
+    def setup
+      @starting_url = "http://starting.url.com/"
+      @yadis_url = "http://starting.url.com/xrds"
+      @services = ["bogus", "not_a_service"]
+
+      @disco_services = Consumer::DiscoveredServices.new(@starting_url,
+                                                         @yadis_url,
+                                                         @services.dup)
+    end
+
+    def test_next
+      assert_equal(@disco_services.next, @services[0])
+      assert_equal(@disco_services.current, @services[0])
+
+      assert_equal(@disco_services.next, @services[1])
+      assert_equal(@disco_services.current, @services[1])
+
+      assert_equal(@disco_services.next, nil)
+      assert_equal(@disco_services.current, nil)
+    end
+
+    def test_for_url
+      assert(@disco_services.for_url?(@starting_url))
+      assert(@disco_services.for_url?(@yadis_url))
+
+      assert(!@disco_services.for_url?(nil))
+      assert(!@disco_services.for_url?("invalid"))
+    end
+
+    def test_started
+      assert(!@disco_services.started?)
+      @disco_services.next
+      assert(@disco_services.started?)
+      @disco_services.next
+      assert(@disco_services.started?) 
+      @disco_services.next
+      assert(!@disco_services.started?)
+    end
+
+    def test_empty
+      assert(Consumer::DiscoveredServices.new(nil, nil, []).empty?)
+
+      assert(!@disco_services.empty?)
+
+      @disco_services.next
+      @disco_services.next
+
+      assert(@disco_services.started?)
+    end
+  end
+
+  # I need to be able to test the protected methods; this lets me do
+  # that.
+  class PassthroughDiscoveryManager < Consumer::DiscoveryManager
+    def method_missing(m, *args)
+      method(m).call(*args)
+    end
+  end
+
+  class TestDiscoveryManager < Test::Unit::TestCase
+    def setup
+      @session = {}
+      @url = "http://unittest.com/"
+      @key_suffix = "testing"
+      @yadis_url = "http://unittest.com/xrds"
+      @manager = PassthroughDiscoveryManager.new(@session, @url, @key_suffix)
+      @key = @manager.session_key
+    end
+
+    def test_construct
+      # Make sure the default session key suffix is not nil.
+      m = Consumer::DiscoveryManager.new(nil, nil)
+      assert(!m.instance_variable_get("@session_key_suffix").nil?)
+
+      m = Consumer::DiscoveryManager.new(nil, nil, "override")
+      assert_equal(m.instance_variable_get("@session_key_suffix"), "override")
+    end
+
+    def test_get_next_service
+      assert_equal(@session[@key], nil)
+
+      next_service = @manager.get_next_service {
+        [@yadis_url, ["one", "two", "three"]]
+      }
+
+      disco = @session[@key]
+      assert_equal(disco.current, "one")
+      assert_equal(next_service, "one")
+      assert(disco.for_url?(@url))
+      assert(disco.for_url?(@yadis_url))
+
+      # The first two calls to get_next_service should return the
+      # services in @disco.
+      assert_equal(@manager.get_next_service, "two")
+      assert_equal(@manager.get_next_service, "three")
+      assert_equal(@session[@key], disco)
+
+      # The manager is exhausted and should be deleted and a new one
+      # should be created.
+      @manager.get_next_service {
+        [@yadis_url, ["four"]]
+      }
+
+      disco2 = @session[@key]
+      assert_equal(disco2.current, "four")
+
+      # create_manager may return a nil manager, in which case the
+      # next service should be nil.
+      @manager.extend(OpenID::InstanceDefExtension)
+      @manager.instance_def(:create_manager) do |yadis_url, services|
+        nil
+      end
+
+      result = @manager.get_next_service { |url|
+        ["unused", []]
+      }
+
+      assert_equal(result, nil)
+    end
+
+    def test_cleanup
+      # With no preexisting manager, cleanup() returns nil.
+      assert_equal(@manager.cleanup, nil)
+
+      # With a manager, it returns the manager's current service.
+      disco = Consumer::DiscoveredServices.new(@url, @yadis_url, ["one", "two"])
+
+      @session[@key] = disco
+      assert_equal(@manager.cleanup, nil)
+      assert_equal(@session[@key], nil)
+
+      @session[@key] = disco
+      disco.next
+      assert_equal(@manager.cleanup, "one")
+      assert_equal(@session[@key], nil)
+
+      # The force parameter should be passed through to get_manager
+      # and destroy_manager.
+      force_value = "yo"
+      testcase = self
+
+      m = Consumer::DiscoveredServices.new(nil, nil, ["inner"])
+      m.next
+
+      @manager.extend(OpenID::InstanceDefExtension)
+      @manager.instance_def(:get_manager) do |force|
+        testcase.assert_equal(force, force_value)
+        m
+      end
+
+      @manager.instance_def(:destroy_manager) do |force|
+        testcase.assert_equal(force, force_value)
+      end
+
+      assert_equal("inner", @manager.cleanup(force_value))
+    end
+
+    def test_get_manager
+      # get_manager should always return the loaded manager when
+      # forced.
+      @session[@key] = "bogus"
+      assert_equal("bogus", @manager.get_manager(true))
+
+      # When not forced, only managers for @url should be returned.
+      disco = Consumer::DiscoveredServices.new(@url, @yadis_url, ["one"])
+      @session[@key] = disco
+      assert_equal(@manager.get_manager, disco)
+
+      # Try to get_manager for a manger that doesn't manage @url:
+      disco2 = Consumer::DiscoveredServices.new("http://not.this.url.com/",
+                                                "http://other.yadis.url/", ["one"])
+      @session[@key] = disco2
+      assert_equal(@manager.get_manager, nil)
+      assert_equal(@manager.get_manager(true), disco2)
+    end
+
+    def test_create_manager
+      assert(@session[@key].nil?)
+
+      services = ["created", "manager"]
+      returned_disco = @manager.create_manager(@yadis_url, services)
+
+      stored_disco = @session[@key]
+      assert(stored_disco.for_url?(@yadis_url))
+      assert_equal(stored_disco.next, "created")
+
+      assert_equal(stored_disco, returned_disco)
+
+      # Calling create_manager with a preexisting manager should
+      # result in StandardError.
+      assert_raise(StandardError) {
+        @manager.create_manager(@yadis_url, services)
+      }
+
+      # create_manager should do nothing (and return nil) if given no
+      # services.
+      @session[@key] = nil
+      result = @manager.create_manager(@yadis_url, [])
+      assert(result.nil?)
+      assert(@session[@key].nil?)
+    end
+
+    class DestroyCalledException < StandardError; end
+
+    def test_destroy_manager
+      # destroy_manager should remove the manager from the session,
+      # forcibly if necessary.
+      valid_disco = Consumer::DiscoveredServices.new(@url, @yadis_url, ["serv"])
+      invalid_disco = Consumer::DiscoveredServices.new("http://not.mine.com/",
+                                                       "http://different.url.com/",
+                                                       ["serv"])
+
+      @session[@key] = valid_disco
+      @manager.destroy_manager
+      assert(@session[@key].nil?)
+
+      @session[@key] = invalid_disco
+      @manager.destroy_manager
+      assert_equal(@session[@key], invalid_disco)
+
+      # Force destruction of manager, no matter which URLs it's for.
+      @manager.destroy_manager(true)
+      assert(@session[@key].nil?)
+    end
+
+    def test_session_key
+      assert(@manager.session_key.ends_with?(
+               @manager.instance_variable_get("@session_key_suffix")))
+    end
+
+    def test_store
+      thing = "opaque"
+      assert(@session[@key].nil?)
+      @manager.store(thing)
+      assert_equal(@session[@key], thing)
+    end
+
+    def test_load
+      thing = "opaque"
+      @session[@key] = thing
+      assert_equal(@manager.load, thing)
+    end
+
+    def test_destroy!
+      thing = "opaque"
+      @manager.store(thing)
+      assert_equal(@manager.load, thing)
+      @manager.destroy!
+      assert(@session[@key].nil?)
+      assert(@manager.load.nil?)
+    end
+  end
+end
diff --git a/vendor/gems/ruby-openid-2.1.4/test/test_extension.rb b/vendor/gems/ruby-openid-2.1.4/test/test_extension.rb
new file mode 100644 (file)
index 0000000..bfe14e9
--- /dev/null
@@ -0,0 +1,46 @@
+require 'openid/extension'
+require 'openid/message'
+require 'test/unit'
+
+module OpenID
+  class DummyExtension < OpenID::Extension
+    TEST_URI = 'http://an.extension'
+    TEST_ALIAS = 'dummy'
+    def initialize
+      @ns_uri = TEST_URI
+      @ns_alias = TEST_ALIAS
+    end
+
+    def get_extension_args
+      return {}
+    end
+  end
+
+  class ToMessageTest < Test::Unit::TestCase
+     def test_OpenID1
+       oid1_msg = Message.new(OPENID1_NS)
+       ext = DummyExtension.new
+       ext.to_message(oid1_msg)
+       namespaces = oid1_msg.namespaces
+       assert(namespaces.implicit?(DummyExtension::TEST_URI))
+       assert_equal(
+                    DummyExtension::TEST_URI,
+                    namespaces.get_namespace_uri(DummyExtension::TEST_ALIAS))
+       assert_equal(DummyExtension::TEST_ALIAS,
+                    namespaces.get_alias(DummyExtension::TEST_URI))
+     end
+     def test_OpenID2
+       oid2_msg = Message.new(OPENID2_NS)
+       ext = DummyExtension.new
+       ext.to_message(oid2_msg)
+       namespaces = oid2_msg.namespaces
+       assert(!namespaces.implicit?(DummyExtension::TEST_URI))
+       assert_equal(
+             DummyExtension::TEST_URI,
+             namespaces.get_namespace_uri(DummyExtension::TEST_ALIAS))
+       assert_equal(DummyExtension::TEST_ALIAS,
+                    namespaces.get_alias(DummyExtension::TEST_URI))
+     end
+   end
+end
diff --git a/vendor/gems/ruby-openid-2.1.4/test/test_extras.rb b/vendor/gems/ruby-openid-2.1.4/test/test_extras.rb
new file mode 100644 (file)
index 0000000..c30861c
--- /dev/null
@@ -0,0 +1,35 @@
+require 'test/unit'
+require 'openid/extras'
+
+class StartsWithTestCase < Test::Unit::TestCase
+    def test_starts_with
+        [["anything", ""],
+         ["something else", ""],
+         ["", ""],
+         ["foos", "foo"],
+        ].each{|str,target| assert(str.starts_with?(target))}
+    end
+
+    def test_not_starts_with
+        [["x", "y"],
+         ["foos", "ball"],
+         ["xx", "xy"],
+        ].each{|str,target| assert(!(str.starts_with? target)) }
+    end
+
+    def test_ends_with
+        [["anything", ""],
+         ["something else", " else"],
+         ["", ""],
+         ["foos", "oos"],
+        ].each{|str,target| assert(str.ends_with?(target))}
+    end
+
+    def test_not_ends_with
+        [["x", "y"],
+         ["foos", "ball"],
+         ["xx", "xy"],
+         ["foosball", "foosbal"],
+        ].each{|str,target| assert(!(str.ends_with? target)) }
+    end
+end
diff --git a/vendor/gems/ruby-openid-2.1.4/test/test_fetchers.rb b/vendor/gems/ruby-openid-2.1.4/test/test_fetchers.rb
new file mode 100644 (file)
index 0000000..7130da5
--- /dev/null
@@ -0,0 +1,538 @@
+require 'test/unit'
+require 'net/http'
+require 'webrick'
+
+require 'testutil'
+require 'util'
+
+require 'openid/fetchers'
+
+require 'stringio'
+
+begin
+  require 'net/https'
+rescue LoadError
+  # We need these names for testing.
+
+  module OpenSSL
+    module SSL
+      class SSLError < StandardError; end
+    end
+  end
+end
+
+module HttpResultAssertions
+  def assert_http_result_is(expected, result)
+    assert_equal expected.code, result.code
+    assert_equal expected.body, result.body
+    assert_equal expected.final_url, result.final_url
+  end
+end
+
+class BogusFetcher
+  RESPONSE = "bogus"
+
+  def fetch(url, body=nil, headers=nil, redirect_limit=5)
+    return BogusFetcher::RESPONSE
+  end
+end
+
+class FetcherTestCase < Test::Unit::TestCase
+  include HttpResultAssertions
+  include OpenID::TestUtil
+
+  @@test_header_name = 'X-test-header'
+  @@test_header_value = 'marmoset'
+
+  class ExpectedResponse < Net::HTTPResponse
+    attr_reader :final_url
+
+    def initialize(code, final_url, body="the expected body",
+                   httpv="1.1", msg=nil)
+      super(httpv, code, msg)
+      @code = code
+      @body = body
+      @final_url = final_url
+    end
+
+    def body
+      @body
+    end
+  end
+
+  @@cases =
+    [
+     # path, status code, expected url (nil = default to path)
+     ['/success', 200, nil],
+     ['/notfound', 404, nil],
+     ['/badreq', 400, nil],
+     ['/forbidden', 403, nil],
+     ['/error', 500, nil],
+     ['/server_error', 503, nil],
+     ['/301redirect', 200, '/success'],
+     ['/302redirect', 200, '/success'],
+     ['/303redirect', 200, '/success'],
+     ['/307redirect', 200, '/success'],
+    ]
+
+  def _redirect_with_code(code)
+    lambda { |req, resp|
+      resp.status = code
+      resp['Location'] = _uri_build('/success')
+    }
+  end
+
+  def _respond_with_code(code)
+    lambda { |req, resp|
+      resp.status = code
+      resp.body = "the expected body"
+    }
+  end
+
+  def _require_header
+    lambda { |req, resp|
+      assert_equal @@test_header_value, req[@@test_header_name]
+      assert_match 'ruby-openid', req['User-agent']
+    }
+  end
+
+  def _require_post
+    lambda { |req, resp|
+      assert_equal 'POST', req.request_method
+      assert_equal "postbody\n", req.body
+    }
+  end
+
+  def _redirect_loop
+    lambda { |req, resp|
+      @_redirect_counter += 1
+      resp.status = 302
+      resp['Location'] = _uri_build('/redirect_loop')
+      resp.body = "Fetched #{@_redirect_counter} times."
+      assert_block("Fetched too many times.") { @_redirect_counter < 10 }
+    }
+  end
+  
+  def setup
+    @fetcher = OpenID::StandardFetcher.new
+    @logfile = StringIO.new
+    @weblog = WEBrick::Log.new(logfile=@logfile)
+    @server = WEBrick::HTTPServer.new(:Port => 0,
+                                      :Logger => @weblog,
+                                      :AccessLog => [])
+    @server_thread = Thread.new {
+      @server.mount_proc('/success', _respond_with_code(200))
+      @server.mount_proc('/301redirect', _redirect_with_code(301))
+      @server.mount_proc('/302redirect', _redirect_with_code(302))
+      @server.mount_proc('/303redirect', _redirect_with_code(303))
+      @server.mount_proc('/307redirect', _redirect_with_code(307))
+      @server.mount_proc('/badreq', _respond_with_code(400))
+      @server.mount_proc('/forbidden', _respond_with_code(403))
+      @server.mount_proc('/notfound', _respond_with_code(404))
+      @server.mount_proc('/error', _respond_with_code(500))
+      @server.mount_proc('/server_error', _respond_with_code(503))
+      @server.mount_proc('/require_header', _require_header)
+      @server.mount_proc('/redirect_to_reqheader') { |req, resp|
+        resp.status = 302
+        resp['Location'] = _uri_build('/require_header')
+      }
+      @server.mount_proc('/post', _require_post)
+      @server.mount_proc('/redirect_loop', _redirect_loop)
+      @server.start
+    }
+    @uri = _uri_build
+    sleep 0.2
+  end
+
+  def _uri_build(path='/')
+    u = URI::HTTP.build({
+                          :host => @server.config[:ServerName],
+                          :port => @server.config[:Port],
+                          :path => path,
+                        })
+    return u.to_s
+  end
+
+  def teardown
+    @server.shutdown
+    # Sleep a little because sometimes this blocks forever.
+    @server_thread.join
+  end
+
+=begin
+# XXX This test no longer works since we're not dealing with URI
+# objects internally.
+  def test_final_url_tainted
+    uri = _uri_build('/301redirect')
+    result = @fetcher.fetch(uri)
+
+    final_url = URI::parse(result.final_url)
+
+    assert final_url.host.tainted?
+    assert final_url.path.tainted?
+  end
+=end
+
+  def test_headers
+    headers = {
+      @@test_header_name => @@test_header_value
+    }
+    uri = _uri_build('/require_header')
+    result = @fetcher.fetch(uri, nil, headers)
+    # The real test runs under the WEBrick handler _require_header,
+    # this just checks the return code from that.
+    assert_equal '200', result.code, @logfile.string
+  end
+
+  def test_headers_after_redirect
+    headers = {
+      @@test_header_name => @@test_header_value
+    }
+    uri = _uri_build('/redirect_to_reqheader')
+    result = @fetcher.fetch(uri, nil, headers)
+    # The real test runs under the WEBrick handler _require_header,
+    # this just checks the return code from that.
+    assert_equal '200', result.code, @logfile.string
+  end
+
+  def test_post
+    uri = _uri_build('/post')
+    result = @fetcher.fetch(uri, "postbody\n")
+    # The real test runs under the WEBrick handler _require_header,
+    # this just checks the return code from that.
+    assert_equal '200', result.code, @logfile.string
+  end
+
+  def test_redirect_limit
+    @_redirect_counter = 0
+    uri = _uri_build('/redirect_loop')
+    assert_raise(OpenID::HTTPRedirectLimitReached) {
+      @fetcher.fetch(uri)
+    }
+  end
+
+  def test_cases
+    for path, expected_code, expected_url in @@cases
+      uri = _uri_build(path)
+      if expected_url.nil?
+        expected_url = uri
+      else
+        expected_url = _uri_build(expected_url)
+      end
+
+      expected = ExpectedResponse.new(expected_code.to_s, expected_url)
+      result = @fetcher.fetch(uri)
+
+      begin
+        assert_http_result_is expected, result
+      rescue Test::Unit::AssertionFailedError => err
+        if result.code == '500' && expected_code != 500
+          # Looks like our WEBrick harness broke.
+          msg = <<EOF
+Status #{result.code} from case #{path}.  Logs:
+#{@logfile.string}
+EOF
+          raise msg
+        end
+
+        # Wrap failure messages so we can tell which case failed.
+        new_msg = "#{path}: #{err.message.to_s}"
+        new_err = Test::Unit::AssertionFailedError.new(new_msg)
+        new_err.set_backtrace(err.backtrace)
+        raise new_err
+      end
+    end
+  end
+
+  def test_https_no_openssl
+    # Override supports_ssl? to always claim that connections don't
+    # support SSL.  Test the behavior of fetch() for HTTPS URLs in
+    # that case.
+    f = OpenID::StandardFetcher.new
+    f.extend(OpenID::InstanceDefExtension)
+
+    f.instance_def(:supports_ssl?) do |conn|
+      false
+    end
+
+    begin
+      f.fetch("https://someurl.com/")
+      flunk("Expected RuntimeError")
+    rescue RuntimeError => why
+      assert_equal(why.to_s, "SSL support not found; cannot fetch https://someurl.com/")
+    end
+  end
+
+  class FakeConnection < Net::HTTP
+    attr_reader :use_ssl, :ca_file
+
+    def initialize *args
+      super
+      @ca_file = nil
+    end
+
+    def use_ssl=(v)
+      @use_ssl = v
+    end
+
+    def ca_file=(ca_file)
+      @ca_file = ca_file
+    end
+  end
+
+  def test_ssl_with_ca_file
+    f = OpenID::StandardFetcher.new
+    ca_file = "BOGUS"
+    f.ca_file = ca_file
+
+    f.extend(OpenID::InstanceDefExtension)
+    f.instance_def(:make_http) do |uri|
+      FakeConnection.new(uri.host, uri.port)
+    end
+
+    testcase = self
+
+    f.instance_def(:set_verified) do |conn, verified|
+      testcase.assert(verified)
+    end
+
+    conn = f.make_connection(URI::parse("https://someurl.com"))
+    assert_equal(conn.ca_file, ca_file)
+  end
+
+  def test_ssl_without_ca_file
+    f = OpenID::StandardFetcher.new
+
+    f.extend(OpenID::InstanceDefExtension)
+    f.instance_def(:make_http) do |uri|
+      FakeConnection.new(uri.host, uri.port)
+    end
+
+    testcase = self
+
+    f.instance_def(:set_verified) do |conn, verified|
+      testcase.assert(!verified)
+    end
+
+    conn = nil
+    assert_log_matches(/making https request to https:\/\/someurl.com without verifying/) {
+      conn = f.make_connection(URI::parse("https://someurl.com"))
+    }
+
+    assert(conn.ca_file.nil?)
+  end
+
+  def test_make_http_nil
+    f = OpenID::StandardFetcher.new
+
+    f.extend(OpenID::InstanceDefExtension)
+    f.instance_def(:make_http) do |uri|
+      nil
+    end
+
+    assert_raise(RuntimeError) {
+      f.make_connection(URI::parse("http://example.com/"))
+    }
+  end
+
+  def test_make_http_invalid
+    f = OpenID::StandardFetcher.new
+
+    f.extend(OpenID::InstanceDefExtension)
+    f.instance_def(:make_http) do |uri|
+      "not a Net::HTTP object"
+    end
+
+    assert_raise(RuntimeError) {
+      f.make_connection(URI::parse("http://example.com/"))
+    }
+  end
+
+  class BrokenSSLConnection
+    def start(&block)
+      raise OpenSSL::SSL::SSLError
+    end
+  end
+
+  def test_sslfetchingerror
+    f = OpenID::StandardFetcher.new
+
+    f.extend(OpenID::InstanceDefExtension)
+    f.instance_def(:make_connection) do |uri|
+      BrokenSSLConnection.new
+    end
+
+    assert_raise(OpenID::SSLFetchingError) {
+      f.fetch("https://bogus.com/")
+    }
+  end
+
+  class TimeoutConnection
+    def start(&block)
+      raise Timeout::Error
+    end
+  end
+
+  def test_fetchingerror
+    f = OpenID::StandardFetcher.new
+
+    f.extend(OpenID::InstanceDefExtension)
+    f.instance_def(:make_connection) do |uri|
+      TimeoutConnection.new
+    end
+
+    assert_raise(OpenID::FetchingError) {
+      f.fetch("https://bogus.com/")
+    }
+  end
+  
+  class TestingException < OpenID::FetchingError; end
+
+  class NoSSLSupportConnection
+    def supports_ssl?
+      false
+    end
+
+    def start
+      yield
+    end
+
+    def request_get(*args)
+      raise TestingException
+    end
+
+    def post_connection_check(hostname)
+      raise RuntimeError
+    end
+
+    def use_ssl?
+      true
+    end
+  end
+
+  class NoUseSSLConnection < NoSSLSupportConnection
+    def use_ssl?
+      false
+    end
+  end
+
+  def test_post_connection_check_no_support_ssl
+    f = OpenID::StandardFetcher.new
+
+    f.extend(OpenID::InstanceDefExtension)
+    f.instance_def(:make_connection) do |uri|
+      NoSSLSupportConnection.new
+    end
+
+    # post_connection_check should not be called.
+    assert_raise(TestingException) {
+      f.fetch("https://bogus.com/")
+    }
+  end
+
+  def test_post_connection_check_no_use_ssl
+    f = OpenID::StandardFetcher.new
+
+    f.extend(OpenID::InstanceDefExtension)
+    f.instance_def(:make_connection) do |uri|
+      NoUseSSLConnection.new
+    end
+
+    # post_connection_check should not be called.
+    assert_raise(TestingException) {
+      f.fetch("https://bogus.com/")
+    }
+  end
+
+  class PostConnectionCheckException < OpenID::FetchingError; end
+
+  class UseSSLConnection < NoSSLSupportConnection
+    def use_ssl?
+      true
+    end
+
+    def post_connection_check(hostname)
+      raise PostConnectionCheckException
+    end
+  end
+
+  def test_post_connection_check
+    f = OpenID::StandardFetcher.new
+
+    f.extend(OpenID::InstanceDefExtension)
+    f.instance_def(:make_connection) do |uri|
+      UseSSLConnection.new
+    end
+
+    f.instance_def(:supports_ssl?) do |conn|
+      true
+    end
+
+    # post_connection_check should be called.
+    assert_raise(PostConnectionCheckException) {
+      f.fetch("https://bogus.com/")
+    }
+  end
+end
+
+class DefaultFetcherTest < Test::Unit::TestCase
+  def setup
+    OpenID.fetcher = nil
+  end
+
+  def test_default_fetcher
+    assert(OpenID.fetcher.is_a?(OpenID::StandardFetcher))
+
+    # A custom fetcher can be set
+    OpenID.fetcher = BogusFetcher.new
+
+    # A test fetch should call the new fetcher
+    assert(OpenID.fetch('not-a-url') == BogusFetcher::RESPONSE)
+
+    # Set the fetcher to nil again
+    OpenID.fetcher = nil
+    assert(OpenID.fetcher.is_a?(OpenID::StandardFetcher))
+  end
+end
+
+class ProxyTest < Test::Unit::TestCase
+  def test_proxy_unreachable
+    begin
+      f = OpenID::StandardFetcher.new('127.0.0.1', 1)
+      # If this tries to connect to the proxy (on port 1), I expect
+      # a 'connection refused' error.  If it tries to contact the below
+      # URI first, it will get some other sort of error.
+      f.fetch("http://unittest.invalid")
+    rescue OpenID::FetchingError => why
+      # XXX: Is this a translatable string that is going to break?
+      if why.message =~ /Connection refused/
+        return
+      end
+      raise why
+    end
+    flunk "expected Connection Refused, but it passed."
+  end
+
+  def test_proxy_env
+    ENV['http_proxy'] = 'http://127.0.0.1:3128/'
+    OpenID.fetcher_use_env_http_proxy
+    
+    # make_http just to give us something with readable attributes to inspect.
+    conn = OpenID.fetcher.make_http(URI.parse('http://127.0.0.2'))
+    assert_equal('127.0.0.1', conn.proxy_address)
+    assert_equal(3128, conn.proxy_port)
+  end
+  # These aren't fully automated tests, but if you start a proxy
+  # on port 8888 (tinyproxy's default) and check its logs...
+#   def test_proxy
+#     f = OpenID::StandardFetcher.new('127.0.0.1', 8888)
+#     result = f.fetch("http://www.example.com/")
+#     assert_match(/RFC.*2606/, result.body)
+#   end
+
+#   def test_proxy_https
+#     f = OpenID::StandardFetcher.new('127.0.0.1', 8888)
+#     result = f.fetch("https://www.myopenid.com/")
+#     assert_match(/myOpenID/, result.body)
+#   end
+end
diff --git a/vendor/gems/ruby-openid-2.1.4/test/test_filters.rb b/vendor/gems/ruby-openid-2.1.4/test/test_filters.rb
new file mode 100644 (file)
index 0000000..990201f
--- /dev/null
@@ -0,0 +1,270 @@
+
+require 'test/unit'
+require 'openid/yadis/filters'
+
+module OpenID
+  class BasicServiceEndpointTest < Test::Unit::TestCase
+    def test_match_types
+      # Make sure the match_types operation returns the expected
+      # results with various inputs.
+      types = ["urn:bogus", "urn:testing"]
+      yadis_url = "http://yadis/"
+
+      no_types_endpoint = Yadis::BasicServiceEndpoint.new(yadis_url, [], nil, nil)
+
+      some_types_endpoint = Yadis::BasicServiceEndpoint.new(yadis_url, types,
+                                                            nil, nil)
+
+      assert(no_types_endpoint.match_types([]) == [])
+      assert(no_types_endpoint.match_types(["urn:absent"]) == [])
+
+      assert(some_types_endpoint.match_types([]) == [])
+      assert(some_types_endpoint.match_types(["urn:absent"]) == [])
+      assert(some_types_endpoint.match_types(types) == types)
+      assert(some_types_endpoint.match_types([types[1], types[0]]) == types)
+      assert(some_types_endpoint.match_types([types[0]]) == [types[0]])
+      assert(some_types_endpoint.match_types(types + ["urn:absent"]) == types)
+    end
+
+    def test_from_basic_service_endpoint
+      # Check BasicServiceEndpoint.from_basic_service_endpoint
+      endpoint = "unused"
+      e = Yadis::BasicServiceEndpoint.new(nil, [], nil, nil)
+
+      assert(Yadis::BasicServiceEndpoint.from_basic_service_endpoint(endpoint) ==
+             endpoint)
+      assert(e.from_basic_service_endpoint(endpoint) ==
+             endpoint)
+    end
+  end
+
+  class TransformFilterMakerTest < Test::Unit::TestCase
+    def make_service_element(types, uris)
+      service = REXML::Element.new('Service')
+      types.each { |type_text|
+        service.add_element('Type').text = type_text
+      }
+      uris.each { |uri_text|
+        service.add_element('URI').text = uri_text
+      }
+      return service
+    end
+    def test_get_service_endpoints
+      yadis_url = "http://yad.is/"
+      uri = "http://uri/"
+      type_uris = ["urn:type1", "urn:type2"]
+      element = make_service_element(type_uris, [uri])
+
+      data = [
+              [type_uris, uri, element],
+             ]
+
+      filters = [Proc.new { |endpoint|
+                   if endpoint.service_element == element
+                     endpoint
+                   else
+                     nil
+                   end
+                 }
+                ]
+
+      tf = Yadis::TransformFilterMaker.new(filters)
+      result = tf.get_service_endpoints(yadis_url, element)
+
+      assert_equal(result[0].yadis_url, yadis_url, result)
+      assert_equal(result[0].uri, uri, result)
+    end
+
+    def test_empty_transform_filter
+      # A transform filter with no filter procs should return nil.
+      endpoint = "unused"
+      t = Yadis::TransformFilterMaker.new([])
+      assert(t.apply_filters(endpoint).nil?)
+    end
+
+    def test_nil_filter
+      # A transform filter with a single nil filter should return nil.
+      nil_filter = Proc.new { |endpoint| nil }
+      t = Yadis::TransformFilterMaker.new([nil_filter])
+      endpoint = "unused"
+      assert(t.apply_filters(endpoint).nil?)
+    end
+
+    def test_identity_filter
+      # A transform filter with an identity filter should return the
+      # input.
+      identity_filter = Proc.new { |endpoint| endpoint }
+      t = Yadis::TransformFilterMaker.new([identity_filter])
+      endpoint = "unused"
+      assert(t.apply_filters(endpoint) == endpoint)
+    end
+
+    def test_return_different_endpoint
+      # Make sure the result of the filter is returned, rather than
+      # the input.
+      returned_endpoint = "returned endpoint"
+      filter = Proc.new { |endpoint| returned_endpoint }
+      t = Yadis::TransformFilterMaker.new([filter])
+      endpoint = "unused"
+      assert(t.apply_filters(endpoint) == returned_endpoint)
+    end
+
+    def test_multiple_filters
+      # Check filter fallback behavior on different inputs.
+      odd, odd_result = 45, "odd"
+      even, even_result = 46, "even"
+
+      filter_odd = Proc.new { |endpoint|
+        if endpoint % 2 == 1
+          odd_result
+        else
+          nil
+        end
+      }
+
+      filter_even = Proc.new { |endpoint|
+        if endpoint % 2 == 0
+          even_result
+        else
+          nil
+        end
+      }
+
+      t = Yadis::TransformFilterMaker.new([filter_odd, filter_even])
+      assert(t.apply_filters(odd) == odd_result)
+      assert(t.apply_filters(even) == even_result)
+    end
+  end
+
+  class BogusServiceEndpointExtractor
+    def initialize(data)
+      @data = data
+    end
+
+    def get_service_endpoints(yadis_url, service_element)
+      return @data
+    end
+  end
+
+  class CompoundFilterTest < Test::Unit::TestCase
+    def test_get_service_endpoints
+      first = ["bogus", "test"]
+      second = ["third"]
+      all = first + second
+
+      subfilters = [
+                    BogusServiceEndpointExtractor.new(first),
+                    BogusServiceEndpointExtractor.new(second),
+                   ]
+
+      cf = Yadis::CompoundFilter.new(subfilters)
+      assert(cf.get_service_endpoints("unused", "unused") == all)
+    end
+  end
+
+  class MakeFilterTest < Test::Unit::TestCase
+    def test_parts_nil
+      result = Yadis.make_filter(nil)
+      assert(result.is_a?(Yadis::TransformFilterMaker),
+             result)
+    end
+
+    def test_parts_array
+      e1 = Yadis::BasicServiceEndpoint.new(nil, [], nil, nil)
+      e2 = Yadis::BasicServiceEndpoint.new(nil, [], nil, nil)
+
+      result = Yadis.make_filter([e1, e2])
+      assert(result.is_a?(Yadis::TransformFilterMaker),
+             result)
+      assert(result.filter_procs[0] == e1.method('from_basic_service_endpoint'))
+      assert(result.filter_procs[1] == e2.method('from_basic_service_endpoint'))
+    end
+
+    def test_parts_single
+      e = Yadis::BasicServiceEndpoint.new(nil, [], nil, nil)
+      result = Yadis.make_filter(e)
+      assert(result.is_a?(Yadis::TransformFilterMaker),
+             result)
+    end
+  end
+
+  class MakeCompoundFilterTest < Test::Unit::TestCase
+    def test_no_filters
+      result = Yadis.mk_compound_filter([])
+      assert(result.subfilters == [])
+    end
+
+    def test_single_transform_filter
+      f = Yadis::TransformFilterMaker.new([])
+      assert(Yadis.mk_compound_filter([f]) == f)
+    end
+
+    def test_single_endpoint
+      e = Yadis::BasicServiceEndpoint.new(nil, [], nil, nil)
+      result = Yadis.mk_compound_filter([e])
+      assert(result.is_a?(Yadis::TransformFilterMaker))
+
+      # Expect the transform filter to call
+      # from_basic_service_endpoint on the endpoint
+      filter = result.filter_procs[0]
+      assert(filter == e.method('from_basic_service_endpoint'),
+             filter)
+    end
+
+    def test_single_proc
+      # Create a proc that just returns nil for any endpoint
+      p = Proc.new { |endpoint| nil }
+      result = Yadis.mk_compound_filter([p])
+      assert(result.is_a?(Yadis::TransformFilterMaker))
+
+      # Expect the transform filter to call
+      # from_basic_service_endpoint on the endpoint
+      filter = result.filter_procs[0]
+      assert(filter == p)
+    end
+
+    def test_multiple_filters_same_type
+      f1 = Yadis::TransformFilterMaker.new([])
+      f2 = Yadis::TransformFilterMaker.new([])
+
+      # Expect mk_compound_filter to actually make a CompoundFilter
+      # from f1 and f2.
+      result = Yadis.mk_compound_filter([f1, f2])
+
+      assert(result.is_a?(Yadis::CompoundFilter))
+      assert(result.subfilters == [f1, f2])
+    end
+
+    def test_multiple_filters_different_type
+      f1 = Yadis::TransformFilterMaker.new([])
+      f2 = Yadis::BasicServiceEndpoint.new(nil, [], nil, nil)
+      f3 = Proc.new { |endpoint| nil }
+
+      e = Yadis::BasicServiceEndpoint.new(nil, [], nil, nil)
+      f4 = [e]
+
+      # Expect mk_compound_filter to actually make a CompoundFilter
+      # from f1 and f2.
+      result = Yadis.mk_compound_filter([f1, f2, f3, f4])
+
+      assert(result.is_a?(Yadis::CompoundFilter))
+
+      assert(result.subfilters[0] == f1)
+      assert(result.subfilters[1].filter_procs[0] ==
+             e.method('from_basic_service_endpoint'))
+      assert(result.subfilters[2].filter_procs[0] ==
+             f2.method('from_basic_service_endpoint'))
+      assert(result.subfilters[2].filter_procs[1] == f3)
+    end
+
+    def test_filter_type_error
+      # Pass various non-filter objects and make sure the filter
+      # machinery explodes.
+      [nil, ["bogus"], [1], [nil, "bogus"]].each { |thing|
+        assert_raise(TypeError) {
+          Yadis.mk_compound_filter(thing)
+        }
+      }
+    end
+  end
+end
diff --git a/vendor/gems/ruby-openid-2.1.4/test/test_idres.rb b/vendor/gems/ruby-openid-2.1.4/test/test_idres.rb
new file mode 100644 (file)
index 0000000..2abe45b
--- /dev/null
@@ -0,0 +1,906 @@
+require "testutil"
+require "util"
+require "test/unit"
+require "openid/consumer/idres"
+require "openid/protocolerror"
+require "openid/store/memory"
+require "openid/store/nonce"
+
+module OpenID
+  class Consumer
+    class IdResHandler
+
+      # Subclass of IdResHandler that doesn't do verification upon
+      # construction. All of the tests call this, except for the ones
+      # explicitly for id_res.
+      class IdResHandler < OpenID::Consumer::IdResHandler
+        def id_res
+        end
+      end
+
+      class CheckForFieldsTest < Test::Unit::TestCase
+        include ProtocolErrorMixin
+
+        BASE_FIELDS = ['return_to', 'assoc_handle', 'sig', 'signed']
+        OPENID2_FIELDS = BASE_FIELDS + ['op_endpoint']
+        OPENID1_FIELDS = BASE_FIELDS + ['identity']
+
+        OPENID1_SIGNED = ['return_to', 'identity']
+        OPENID2_SIGNED =
+          OPENID1_SIGNED + ['response_nonce', 'claimed_id', 'assoc_handle']
+
+        def mkMsg(ns, fields, signed_fields)
+          msg = Message.new(ns)
+          fields.each do |field|
+            msg.set_arg(OPENID_NS, field, "don't care")
+          end
+          if fields.member?('signed')
+            msg.set_arg(OPENID_NS, 'signed', signed_fields.join(','))
+          end
+          msg
+        end
+
+        1.times do # so as not to bleed into the outer namespace
+          n = 0
+          [[],
+           ['foo'],
+           ['bar', 'baz'],
+          ].each do |signed_fields|
+            test = lambda do
+              msg = mkMsg(OPENID2_NS, OPENID2_FIELDS, signed_fields)
+              idres = IdResHandler.new(msg, nil)
+              assert_equal(signed_fields, idres.send(:signed_list))
+              # Do it again to make sure logic for caching is correct
+              assert_equal(signed_fields, idres.send(:signed_list))
+            end
+            define_method("test_signed_list_#{n += 1}", test)
+          end
+        end
+
+        # test all missing fields for OpenID 1 and 2
+        1.times do
+          [["openid1", OPENID1_NS, OPENID1_FIELDS],
+           ["openid2", OPENID2_NS, OPENID2_FIELDS],
+          ].each do |ver, ns, all_fields|
+            all_fields.each do |field|
+              test = lambda do
+                fields = all_fields.dup
+                fields.delete(field)
+                msg = mkMsg(ns, fields, [])
+                idres = IdResHandler.new(msg, nil)
+                assert_protocol_error("Missing required field #{field}") {
+                  idres.send(:check_for_fields)
+                }
+              end
+              define_method("test_#{ver}_check_missing_#{field}", test)
+            end
+          end
+        end
+
+        # Test all missing signed for OpenID 1 and 2
+        1.times do
+          [["openid1", OPENID1_NS, OPENID1_FIELDS, OPENID1_SIGNED],
+           ["openid2", OPENID2_NS, OPENID2_FIELDS, OPENID2_SIGNED],
+          ].each do |ver, ns, all_fields, signed_fields|
+            signed_fields.each do |signed_field|
+              test = lambda do
+                fields = signed_fields.dup
+                fields.delete(signed_field)
+                msg = mkMsg(ns, all_fields, fields)
+                # Make sure the signed field is actually in the request
+                msg.set_arg(OPENID_NS, signed_field, "don't care")
+                idres = IdResHandler.new(msg, nil)
+                assert_protocol_error("#{signed_field.inspect} not signed") {
+                  idres.send(:check_for_fields)
+                }
+              end
+              define_method("test_#{ver}_check_missing_signed_#{signed_field}", test)
+            end
+          end
+        end
+
+        def test_112
+          args = {'openid.assoc_handle' => 'fa1f5ff0-cde4-11dc-a183-3714bfd55ca8', 
+                  'openid.claimed_id' => 'http://binkley.lan/user/test01', 
+                  'openid.identity' => 'http://test01.binkley.lan/', 
+                  'openid.mode' => 'id_res', 
+                  'openid.ns' => 'http://specs.openid.net/auth/2.0', 
+                  'openid.ns.pape' => 'http://specs.openid.net/extensions/pape/1.0', 
+                  'openid.op_endpoint' => 'http://binkley.lan/server', 
+                  'openid.pape.auth_policies' => 'none', 
+                  'openid.pape.auth_time' => '2008-01-28T20:42:36Z', 
+                  'openid.pape.nist_auth_level' => '0', 
+                  'openid.response_nonce' => '2008-01-28T21:07:04Z99Q=', 
+                  'openid.return_to' => 'http://binkley.lan:8001/process?janrain_nonce=2008-01-28T21%3A07%3A02Z0tMIKx', 
+                  'openid.sig' => 'YJlWH4U6SroB1HoPkmEKx9AyGGg=', 
+                  'openid.signed' => 'assoc_handle,identity,response_nonce,return_to,claimed_id,op_endpoint,pape.auth_time,ns.pape,pape.nist_auth_level,pape.auth_policies' 
+                } 
+          assert_equal(args['openid.ns'], OPENID2_NS)
+          incoming = Message.from_post_args(args)
+          assert(incoming.is_openid2)
+          idres = IdResHandler.new(incoming, nil)
+          car = idres.send(:create_check_auth_request)
+          expected_args = args.dup
+          expected_args['openid.mode'] = 'check_authentication'
+          expected = Message.from_post_args(expected_args)
+          assert(expected.is_openid2)
+          assert_equal(expected, car)
+          assert_equal(expected_args, car.to_post_args)
+        end        
+
+        def test_no_signed_list
+          msg = Message.new(OPENID2_NS)
+          idres = IdResHandler.new(msg, nil)
+          assert_protocol_error("Response missing signed") {
+            idres.send(:signed_list)
+          }
+        end
+
+        def test_success_openid1
+          msg = mkMsg(OPENID1_NS, OPENID1_FIELDS, OPENID1_SIGNED)
+          idres = IdResHandler.new(msg, nil)
+          assert_nothing_raised {
+            idres.send(:check_for_fields)
+          }
+        end
+      end
+
+      class ReturnToArgsTest < Test::Unit::TestCase
+        include OpenID::ProtocolErrorMixin
+
+        def check_return_to_args(query)
+          idres = IdResHandler.new(Message.from_post_args(query), nil)
+          class << idres
+            def verify_return_to_base(unused)
+            end
+          end
+          idres.send(:verify_return_to)
+        end
+
+        def assert_bad_args(msg, query)
+          assert_protocol_error(msg) {
+            check_return_to_args(query)
+          }
+        end
+
+        def test_return_to_args_okay
+          assert_nothing_raised {
+            check_return_to_args({
+              'openid.mode' => 'id_res',
+              'openid.return_to' => 'http://example.com/?foo=bar',
+              'foo' => 'bar',
+              })
+          }
+        end
+
+        def test_unexpected_arg_okay
+          assert_bad_args("Unexpected parameter", {
+              'openid.mode' => 'id_res',
+              'openid.return_to' => 'http://example.com/',
+              'foo' => 'bar',
+              })
+        end
+
+        def test_return_to_mismatch
+          assert_bad_args('Message missing ret', {
+            'openid.mode' => 'id_res',
+            'openid.return_to' => 'http://example.com/?foo=bar',
+            })
+
+          assert_bad_args("Parameter 'foo' val", {
+            'openid.mode' => 'id_res',
+            'openid.return_to' => 'http://example.com/?foo=bar',
+            'foo' => 'foos',
+            })
+        end
+      end
+
+      class ReturnToVerifyTest < Test::Unit::TestCase
+        def test_bad_return_to
+          return_to = "http://some.url/path?foo=bar"
+
+          m = Message.new(OPENID1_NS)
+          m.set_arg(OPENID_NS, 'mode', 'cancel')
+          m.set_arg(BARE_NS, 'foo', 'bar')
+
+          # Scheme, authority, and path differences are checked by
+          # IdResHandler.verify_return_to_base.  Query args checked by
+          # IdResHandler.verify_return_to_args.
+          [
+            # Scheme only
+            "https://some.url/path?foo=bar",
+            # Authority only
+            "http://some.url.invalid/path?foo=bar",
+            # Path only
+            "http://some.url/path_extra?foo=bar",
+            # Query args differ
+            "http://some.url/path?foo=bar2",
+            "http://some.url/path?foo2=bar",
+            ].each do |bad|
+              m.set_arg(OPENID_NS, 'return_to', bad)
+              idres = IdResHandler.new(m, return_to)
+              assert_raises(ProtocolError) {
+                idres.send(:verify_return_to)
+              }
+          end
+        end
+
+        def test_good_return_to
+          base = 'http://example.janrain.com/path'
+          [ [base, {}],
+            [base + "?another=arg", {'another' => 'arg'}],
+            [base + "?another=arg#frag", {'another' => 'arg'}],
+            ['HTTP'+base[4..-1], {}],
+            [base.sub('com', 'COM'), {}],
+            ['http://example.janrain.com:80/path', {}],
+            ['http://example.janrain.com/p%61th', {}],
+            ['http://example.janrain.com/./path',{}],
+          ].each do |return_to, args|
+            args['openid.return_to'] = return_to
+            msg = Message.from_post_args(args)
+            idres = IdResHandler.new(msg, base)
+            assert_nothing_raised {
+              idres.send(:verify_return_to)
+            }
+          end
+        end
+      end
+
+      class DummyEndpoint
+        attr_accessor :server_url
+        def initialize(server_url)
+          @server_url = server_url
+        end
+      end
+
+      class CheckSigTest < Test::Unit::TestCase
+        include ProtocolErrorMixin
+        include TestUtil
+
+        def setup
+          @assoc = GoodAssoc.new('{not_dumb}')
+          @store = Store::Memory.new
+          @server_url = 'http://server.url/'
+          @endpoint = DummyEndpoint.new(@server_url)
+          @store.store_association(@server_url, @assoc)
+
+          @message = Message.from_post_args({
+              'openid.mode' => 'id_res',
+              'openid.identity' => '=example',
+              'openid.sig' => GOODSIG,
+              'openid.assoc_handle' => @assoc.handle,
+              'openid.signed' => 'mode,identity,assoc_handle,signed',
+              'frobboz' => 'banzit',
+              })
+        end
+
+        def call_idres_method(method_name)
+          idres = IdResHandler.new(@message, nil, @store, @endpoint)
+          idres.extend(InstanceDefExtension)
+          yield idres
+          idres.send(method_name)
+        end
+
+        def call_check_sig(&proc)
+          call_idres_method(:check_signature, &proc)
+        end
+
+        def no_check_auth(idres)
+          idres.instance_def(:check_auth) { fail "Called check_auth" }
+        end
+
+        def test_sign_good
+          assert_nothing_raised {
+            call_check_sig(&method(:no_check_auth))
+          }
+        end
+
+        def test_bad_sig
+          @message.set_arg(OPENID_NS, 'sig', 'bad sig!')
+          assert_protocol_error('Bad signature') {
+            call_check_sig(&method(:no_check_auth))
+          }
+        end
+
+        def test_check_auth_ok
+          @message.set_arg(OPENID_NS, 'assoc_handle', 'dumb-handle')
+          check_auth_called = false
+          call_check_sig do |idres|
+            idres.instance_def(:check_auth) do
+              check_auth_called = true
+            end
+          end
+          assert(check_auth_called)
+        end
+
+        def test_check_auth_ok_no_store
+          @store = nil
+          check_auth_called = false
+          call_check_sig do |idres|
+            idres.instance_def(:check_auth) do
+              check_auth_called = true
+            end
+          end
+          assert(check_auth_called)
+        end
+
+        def test_expired_assoc
+          @assoc.expires_in = -1
+          @store.store_association(@server_url, @assoc)
+          assert_protocol_error('Association with') {
+            call_check_sig(&method(:no_check_auth))
+          }
+        end
+
+        def call_check_auth(&proc)
+          assert_log_matches("Using 'check_authentication'") {
+            call_idres_method(:check_auth, &proc)
+          }
+        end
+
+        def test_check_auth_create_fail
+          assert_protocol_error("Could not generate") {
+            call_check_auth do |idres|
+              idres.instance_def(:create_check_auth_request) do
+                raise Message::KeyNotFound, "Testing"
+              end
+            end
+          }
+        end
+
+        def test_check_auth_okay
+          OpenID.extend(OverrideMethodMixin)
+          me = self
+          send_resp = Proc.new do |req, server_url|
+            me.assert_equal(:req, req)
+            :expected_response
+          end
+
+          OpenID.with_method_overridden(:make_kv_post, send_resp) do
+            final_resp = call_check_auth do |idres|
+              idres.instance_def(:create_check_auth_request) {
+                :req
+              }
+              idres.instance_def(:process_check_auth_response) do |resp|
+                me.assert_equal(:expected_response, resp)
+              end
+            end
+          end
+        end
+
+        def test_check_auth_process_fail
+          OpenID.extend(OverrideMethodMixin)
+          me = self
+          send_resp = Proc.new do |req, server_url|
+            me.assert_equal(:req, req)
+            :expected_response
+          end
+
+          OpenID.with_method_overridden(:make_kv_post, send_resp) do
+            assert_protocol_error("Testing") do
+              final_resp = call_check_auth do |idres|
+                idres.instance_def(:create_check_auth_request) { :req }
+                idres.instance_def(:process_check_auth_response) do |resp|
+                  me.assert_equal(:expected_response, resp)
+                  raise ProtocolError, "Testing"
+                end
+              end
+            end
+          end
+        end
+
+        1.times do
+          # Fields from the signed list
+          ['mode', 'identity', 'assoc_handle'
+          ].each do |field|
+            test = lambda do
+              @message.del_arg(OPENID_NS, field)
+              assert_raises(Message::KeyNotFound) {
+                call_idres_method(:create_check_auth_request) {}
+              }
+            end
+            define_method("test_create_check_auth_missing_#{field}", test)
+          end
+        end
+
+        def test_create_check_auth_request_success
+          ca_msg = call_idres_method(:create_check_auth_request) {}
+          expected = @message.copy
+          expected.set_arg(OPENID_NS, 'mode', 'check_authentication')
+          assert_equal(expected, ca_msg)
+        end
+
+      end
+
+      class CheckAuthResponseTest < Test::Unit::TestCase
+        include TestUtil
+        include ProtocolErrorMixin
+
+        def setup
+          @message = Message.from_openid_args({
+            'is_valid' => 'true',
+            })
+          @assoc = GoodAssoc.new
+          @store = Store::Memory.new
+          @server_url = 'http://invalid/'
+          @endpoint =  DummyEndpoint.new(@server_url)
+          @idres = IdResHandler.new(nil, nil, @store, @endpoint)
+        end
+
+        def call_process
+          @idres.send(:process_check_auth_response, @message)
+        end
+
+        def test_valid
+          assert_log_matches() { call_process }
+        end
+
+        def test_invalid
+          for is_valid in ['false', 'monkeys']
+            @message.set_arg(OPENID_NS, 'is_valid', 'false')
+            assert_protocol_error("Server #{@server_url} responds") {
+              assert_log_matches() { call_process }
+            }
+          end
+        end
+
+        def test_valid_invalidate
+          @message.set_arg(OPENID_NS, 'invalidate_handle', 'cheese')
+          assert_log_matches("Received 'invalidate_handle'") { call_process }
+        end
+
+        def test_invalid_invalidate
+          @message.set_arg(OPENID_NS, 'invalidate_handle', 'cheese')
+          for is_valid in ['false', 'monkeys']
+            @message.set_arg(OPENID_NS, 'is_valid', 'false')
+            assert_protocol_error("Server #{@server_url} responds") {
+              assert_log_matches("Received 'invalidate_handle'") {
+                call_process
+              }
+            }
+          end
+        end
+
+        def test_invalidate_no_store
+          @idres.instance_variable_set(:@store, nil)
+          @message.set_arg(OPENID_NS, 'invalidate_handle', 'cheese')
+          assert_log_matches("Received 'invalidate_handle'",
+                             'Unexpectedly got "invalidate_handle"') {
+            call_process
+          }
+        end
+      end
+
+      class NonceTest < Test::Unit::TestCase
+        include TestUtil
+        include ProtocolErrorMixin
+
+        def setup
+          @store = Object.new
+          class << @store
+            attr_accessor :nonces, :succeed
+            def use_nonce(server_url, time, extra)
+              @nonces << [server_url, time, extra]
+              @succeed
+            end
+          end
+          @store.nonces = []
+          @nonce = Nonce.mk_nonce
+        end
+
+        def call_check_nonce(post_args, succeed=false)
+          response = Message.from_post_args(post_args)
+          if !@store.nil?
+            @store.succeed = succeed
+          end
+          idres = IdResHandler.new(response, nil, @store, nil)
+          idres.send(:check_nonce)
+        end
+
+        def test_openid1_success
+          assert_nothing_raised {
+            call_check_nonce({'rp_nonce' => @nonce}, true)
+          }
+        end
+
+        def test_openid1_missing
+          assert_protocol_error('Nonce missing') { call_check_nonce({}) }
+        end
+
+        def test_openid2_ignore_rp_nonce
+          assert_protocol_error('Nonce missing') {
+            call_check_nonce({'rp_nonce' => @nonce,
+                               'openid.ns' => OPENID2_NS})
+          }
+        end
+
+        def test_openid2_success
+          assert_nothing_raised {
+            call_check_nonce({'openid.response_nonce' => @nonce,
+                               'openid.ns' => OPENID2_NS}, true)
+          }
+        end
+
+        def test_openid1_ignore_response_nonce
+          assert_protocol_error('Nonce missing') {
+            call_check_nonce({'openid.response_nonce' => @nonce})
+          }
+        end
+
+        def test_no_store
+          @store = nil
+          assert_nothing_raised {
+            call_check_nonce({'rp_nonce' => @nonce})
+          }
+        end
+
+        def test_already_used
+          assert_protocol_error('Nonce already used') {
+            call_check_nonce({'rp_nonce' => @nonce}, false)
+          }
+        end
+
+        def test_malformed_nonce
+          assert_protocol_error('Malformed nonce') {
+            call_check_nonce({'rp_nonce' => 'whee!'})
+          }
+        end
+      end
+
+      class DiscoveryVerificationTest < Test::Unit::TestCase
+        include ProtocolErrorMixin
+        include TestUtil
+
+        def setup
+          @endpoint = OpenIDServiceEndpoint.new
+        end
+
+        def call_verify(msg_args)
+          call_verify_modify(msg_args){}
+        end
+
+        def call_verify_modify(msg_args)
+          msg = Message.from_openid_args(msg_args)
+          idres = IdResHandler.new(msg, nil, nil, @endpoint)
+          idres.extend(InstanceDefExtension)
+          yield idres
+          idres.send(:verify_discovery_results)
+          idres.instance_variable_get(:@endpoint)
+        end
+
+        def assert_verify_protocol_error(error_prefix, openid_args)
+          assert_protocol_error(error_prefix) {call_verify(openid_args)}
+        end
+
+        def test_openid1_no_local_id
+          @endpoint.claimed_id = 'http://invalid/'
+          assert_verify_protocol_error("Missing required field: "\
+                                       "<#{OPENID1_NS}>identity", {})
+        end
+
+        def test_openid1_no_endpoint
+          @endpoint = nil
+          assert_raises(ProtocolError) {
+            call_verify({'identity' => 'snakes on a plane'})
+          }
+        end
+
+        def test_openid1_fallback_1_0
+          claimed_id = 'http://claimed.id/'
+          @endpoint = nil
+          resp_mesg = Message.from_openid_args({
+            'ns' => OPENID1_NS,
+            'identity' => claimed_id,
+            })
+
+          # Pass the OpenID 1 claimed_id this way since we're passing
+          # None for the endpoint.
+          resp_mesg.set_arg(BARE_NS, 'openid1_claimed_id', claimed_id)
+
+          # We expect the OpenID 1 discovery verification to try
+          # matching the discovered endpoint against the 1.1 type and
+          # fall back to 1.0.
+          expected_endpoint = OpenIDServiceEndpoint.new
+          expected_endpoint.type_uris = [OPENID_1_0_TYPE]
+          expected_endpoint.local_id = nil
+          expected_endpoint.claimed_id = claimed_id
+  
+          hacked_discover = Proc.new {
+            |_claimed_id| ['unused', [expected_endpoint]]
+          }
+          idres = IdResHandler.new(resp_mesg, nil, nil, @endpoint)
+          assert_log_matches('Performing discovery') {
+            OpenID.with_method_overridden(:discover, hacked_discover) {
+              idres.send(:verify_discovery_results)
+            }
+          }
+          actual_endpoint = idres.instance_variable_get(:@endpoint)
+          assert_equal(actual_endpoint, expected_endpoint)
+
+        end
+
+        def test_openid2_no_op_endpoint
+          assert_protocol_error("Missing required field: "\
+                                "<#{OPENID2_NS}>op_endpoint") {
+            call_verify({'ns'=>OPENID2_NS})
+          }
+        end
+
+        def test_openid2_local_id_no_claimed
+          assert_verify_protocol_error('openid.identity is present without',
+                                       {'ns' => OPENID2_NS,
+                                         'op_endpoint' => 'Phone Home',
+                                         'identity' => 'Jorge Lius Borges'})
+        end
+
+        def test_openid2_no_local_id_claimed
+          assert_log_matches() {
+            assert_protocol_error('openid.claimed_id is present without') {
+              call_verify({'ns' => OPENID2_NS,
+                            'op_endpoint' => 'Phone Home',
+                            'claimed_id' => 'Manuel Noriega'})
+            }
+          }
+        end
+
+        def test_openid2_no_identifiers
+          op_endpoint = 'Phone Home'
+          result_endpoint = assert_log_matches() {
+            call_verify({'ns' => OPENID2_NS,
+                          'op_endpoint' => op_endpoint})
+          }
+          assert(result_endpoint.is_op_identifier)
+          assert_equal(op_endpoint, result_endpoint.server_url)
+          assert(result_endpoint.claimed_id.nil?)
+        end
+
+        def test_openid2_no_endpoint_does_disco
+          endpoint = OpenIDServiceEndpoint.new
+          endpoint.claimed_id = 'monkeysoft'
+          @endpoint = nil
+          result = assert_log_matches('No pre-discovered') {
+            call_verify_modify({'ns' => OPENID2_NS,
+                                 'identity' => 'sour grapes',
+                                 'claimed_id' => 'monkeysoft',
+                                 'op_endpoint' => 'Phone Home'}) do |idres|
+              idres.instance_def(:discover_and_verify) do |claimed_id, endpoints|
+                @endpoint = endpoint
+              end
+            end
+          }
+          assert_equal(endpoint, result)
+        end
+
+
+        def test_openid2_mismatched_does_disco
+          @endpoint.claimed_id = 'nothing special, but different'
+          @endpoint.local_id = 'green cheese'
+
+          endpoint = OpenIDServiceEndpoint.new
+          endpoint.claimed_id = 'monkeysoft'
+
+          result = assert_log_matches('Error attempting to use stored',
+                             'Attempting discovery') {
+            call_verify_modify({'ns' => OPENID2_NS,
+                                 'identity' => 'sour grapes',
+                                 'claimed_id' => 'monkeysoft',
+                                 'op_endpoint' => 'Green Cheese'}) do |idres|
+                        idres.extend(InstanceDefExtension)
+              idres.instance_def(:discover_and_verify) do |claimed_id, endpoints|
+                @endpoint = endpoint
+              end
+            end
+          }
+          assert(endpoint.equal?(result))
+        end
+
+        def test_openid2_use_pre_discovered
+          @endpoint.local_id = 'my identity'
+          @endpoint.claimed_id = 'http://i-am-sam/'
+          @endpoint.server_url = 'Phone Home'
+          @endpoint.type_uris = [OPENID_2_0_TYPE]
+
+          result = assert_log_matches() {
+            call_verify({'ns' => OPENID2_NS,
+                          'identity' => @endpoint.local_id,
+                          'claimed_id' => @endpoint.claimed_id,
+                          'op_endpoint' => @endpoint.server_url
+                        })
+          }
+          assert(result.equal?(@endpoint))
+        end
+
+        def test_openid2_use_pre_discovered_wrong_type
+          text = "verify failed"
+          me = self
+
+          @endpoint.local_id = 'my identity'
+          @endpoint.claimed_id = 'i am sam'
+          @endpoint.server_url = 'Phone Home'
+          @endpoint.type_uris = [OPENID_1_1_TYPE]
+          endpoint = @endpoint
+
+          msg = Message.from_openid_args({'ns' => OPENID2_NS,
+                                           'identity' => @endpoint.local_id,
+                                           'claimed_id' =>
+                                           @endpoint.claimed_id,
+                                           'op_endpoint' =>
+                                           @endpoint.server_url})
+
+          idres = IdResHandler.new(msg, nil, nil, @endpoint)
+          idres.extend(InstanceDefExtension)
+          idres.instance_def(:discover_and_verify) { |claimed_id, to_match|
+            me.assert_equal(endpoint.claimed_id, to_match[0].claimed_id)
+            me.assert_equal(claimed_id, endpoint.claimed_id)
+            raise ProtocolError, text
+          }
+          assert_log_matches('Error attempting to use stored',
+                             'Attempting discovery') {
+            assert_protocol_error(text) {
+              idres.send(:verify_discovery_results)
+            }
+          }
+        end
+
+
+        def test_openid1_use_pre_discovered
+          @endpoint.local_id = 'my identity'
+          @endpoint.claimed_id = 'http://i-am-sam/'
+          @endpoint.server_url = 'Phone Home'
+          @endpoint.type_uris = [OPENID_1_1_TYPE]
+
+          result = assert_log_matches() {
+            call_verify({'ns' => OPENID1_NS,
+                          'identity' => @endpoint.local_id})
+          }
+          assert(result.equal?(@endpoint))
+        end
+
+
+        def test_openid1_use_pre_discovered_wrong_type
+          verified_error = Class.new(Exception)
+
+          @endpoint.local_id = 'my identity'
+          @endpoint.claimed_id = 'i am sam'
+          @endpoint.server_url = 'Phone Home'
+          @endpoint.type_uris = [OPENID_2_0_TYPE]
+
+          assert_log_matches('Error attempting to use stored',
+                             'Attempting discovery') {
+            assert_raises(verified_error) {
+              call_verify_modify({'ns' => OPENID1_NS,
+                                   'identity' => @endpoint.local_id}) { |idres|
+                idres.instance_def(:discover_and_verify) do |claimed_id, endpoints|
+                  raise verified_error
+                end
+              }
+            }
+          }
+        end
+
+        def test_openid2_fragment
+          claimed_id = "http://unittest.invalid/"
+          claimed_id_frag = claimed_id + "#fragment"
+
+          @endpoint.local_id = 'my identity'
+          @endpoint.claimed_id = claimed_id
+          @endpoint.server_url = 'Phone Home'
+          @endpoint.type_uris = [OPENID_2_0_TYPE]
+
+          result = assert_log_matches() {
+            call_verify({'ns' => OPENID2_NS,
+                          'identity' => @endpoint.local_id,
+                          'claimed_id' => claimed_id_frag,
+                          'op_endpoint' => @endpoint.server_url})
+          }
+
+          [:local_id, :server_url, :type_uris].each do |sym|
+            assert_equal(@endpoint.send(sym), result.send(sym))
+          end
+          assert_equal(claimed_id_frag, result.claimed_id)
+        end
+
+        def test_endpoint_without_local_id
+          # An endpoint like this with no local_id is generated as a result of
+          # e.g. Yadis discovery with no LocalID tag.
+          @endpoint.server_url = "http://localhost:8000/openidserver"
+          @endpoint.claimed_id = "http://localhost:8000/id/id-jo"
+
+          to_match = OpenIDServiceEndpoint.new
+          to_match.server_url = "http://localhost:8000/openidserver"
+          to_match.claimed_id = "http://localhost:8000/id/id-jo"
+          to_match.local_id = "http://localhost:8000/id/id-jo"
+
+          idres = IdResHandler.new(nil, nil)
+          assert_log_matches() {
+            result = idres.send(:verify_discovery_single, @endpoint, to_match)
+          }
+        end
+      end
+
+      class IdResTopLevelTest < Test::Unit::TestCase
+        def test_id_res
+          endpoint = OpenIDServiceEndpoint.new
+          endpoint.server_url = 'http://invalid/server'
+          endpoint.claimed_id = 'http://my.url/'
+          endpoint.local_id = 'http://invalid/username'
+          endpoint.type_uris = [OPENID_2_0_TYPE]
+
+          assoc = GoodAssoc.new
+          store = Store::Memory.new
+          store.store_association(endpoint.server_url, assoc)
+
+          signed_fields =
+            [
+             'response_nonce',
+             'op_endpoint',
+             'assoc_handle',
+             'identity',
+             'claimed_id',
+             'ns',
+             'return_to',
+            ]
+
+          return_to = 'http://return.to/'
+          args = {
+            'ns' => OPENID2_NS,
+            'return_to' => return_to,
+            'claimed_id' => endpoint.claimed_id,
+            'identity' => endpoint.local_id,
+            'assoc_handle' => assoc.handle,
+            'op_endpoint' => endpoint.server_url,
+            'response_nonce' => Nonce.mk_nonce,
+            'signed' => signed_fields.join(','),
+            'sig' => GOODSIG,
+          }
+          msg = Message.from_openid_args(args)
+          idres = OpenID::Consumer::IdResHandler.new(msg, return_to,
+                                                     store, endpoint)
+          assert_equal(idres.signed_fields,
+                       signed_fields.map {|f|'openid.' + f})
+        end
+      end
+
+
+      class DiscoverAndVerifyTest < Test::Unit::TestCase
+        include ProtocolErrorMixin
+        include TestUtil
+
+        def test_no_services
+          me = self
+          disco = Proc.new do |e|
+            me.assert_equal(e, :sentinel)
+            [:undefined, []]
+          end
+          endpoint = OpenIDServiceEndpoint.new
+          endpoint.claimed_id = :sentinel
+          idres = IdResHandler.new(nil, nil)
+          assert_log_matches('Performing discovery on') do
+            assert_protocol_error('No OpenID information found') do
+              OpenID.with_method_overridden(:discover, disco) do
+                idres.send(:discover_and_verify, :sentinel, [endpoint])
+              end
+            end
+          end
+        end
+      end
+
+      class VerifyDiscoveredServicesTest < Test::Unit::TestCase
+        include ProtocolErrorMixin
+        include TestUtil
+
+        def test_no_services
+          endpoint = OpenIDServiceEndpoint.new
+          endpoint.claimed_id = :sentinel
+          idres = IdResHandler.new(nil, nil)
+          assert_log_matches('Discovery verification failure') do
+            assert_protocol_error('No matching endpoint') do
+              idres.send(:verify_discovered_services,
+                         'http://bogus.id/', [], [endpoint])
+            end
+          end
+        end
+      end
+    end
+  end
+end
diff --git a/vendor/gems/ruby-openid-2.1.4/test/test_kvform.rb b/vendor/gems/ruby-openid-2.1.4/test/test_kvform.rb
new file mode 100644 (file)
index 0000000..9226057
--- /dev/null
@@ -0,0 +1,165 @@
+require 'test/unit'
+require 'openid/kvform'
+require 'openid/util'
+require 'util'
+
+include OpenID
+
+class KVFormTests < Test::Unit::TestCase
+  include OpenID::TestUtil
+
+  def test_kvdict
+    [
+     # (kvform, parsed dictionary, expected warnings)
+     ["", {}, 0],
+     ["\n  \n     \n", {}, 0],
+     ["college:harvey mudd\n", {"college" => "harvey mudd"}, 0],
+     ["city:claremont\nstate:CA\n",
+      {"city" => "claremont", "state" => "CA"}, 0],
+     ["is_valid:true\ninvalidate_handle:{HMAC-SHA1:2398410938412093}\n",
+      {"is_valid" => "true",
+        "invalidate_handle" => "{HMAC-SHA1:2398410938412093}"}, 0],
+
+     # Warnings from lines with no colon:
+     ["x\n", {}, 1],
+     ["x\nx\n", {}, 2],
+     ["East is least\n", {}, 1],
+
+     # But not from blank lines (because LJ generates them)
+     ["x\n\n", {}, 1],
+
+     # Warning from empty key
+     [":\n", {''=>''}, 1],
+     [":missing key\n", {''=>'missing key'}, 1],
+
+     # Warnings from leading or trailing whitespace in key or value
+     [" street:foothill blvd\n", {"street"=>"foothill blvd"}, 1],
+     ["major: computer science\n", {"major"=>"computer science"}, 1],
+     [" dorm : east \n", {"dorm"=>"east"}, 2],
+
+     # Warnings from missing trailing newline
+     ["e^(i*pi)+1:0", {"e^(i*pi)+1" => "0"}, 1],
+     ["east:west\nnorth:south", {"east"=>"west", "north"=>"south"}, 1],
+    ].each { |case_|
+      _run_kvdictTest(case_)
+    }
+  end
+
+  def _run_kvdictTest(case_)
+    kv, dct, warnings = case_
+
+    d = nil
+    d2 = nil
+    assert_log_line_count(warnings) {
+      # Convert KVForm to dict
+      d = Util.kv_to_dict(kv)
+
+      # Strict mode should raise KVFormError instead of logging
+      # messages
+      if warnings > 0
+        assert_raise(KVFormError) do
+          Util.kv_to_seq(kv, true)
+        end
+      end
+
+      # make sure it parses to expected dict
+      assert_equal(dct, d)
+    }
+
+    # Convert back to KVForm and round-trip back to dict to make sure
+    # that *** dict -> kv -> dict is identity. ***
+    kv = Util.dict_to_kv(d)
+
+    silence_logging {
+      d2 = Util.kv_to_dict(kv)
+    }
+
+    assert_equal(d, d2)
+  end
+
+  def test_kvseq
+    [
+     [[], "", 0],
+
+     [[["openid", "useful"], ["a", "b"]], "openid:useful\na:b\n", 0],
+
+     # Warnings about leading whitespace
+     [[[" openid", "useful"], ["a", "b"]], " openid:useful\na:b\n", 2],
+
+     # Warnings about leading and trailing whitespace
+     [[[" openid ", " useful "],
+       [" a ", " b "]], " openid : useful \n a : b \n", 8],
+
+     # warnings about leading and trailing whitespace, but not about
+     # internal whitespace.
+     [[[" open id ", " use ful "],
+       [" a ", " b "]], " open id : use ful \n a : b \n", 8],
+
+     [[["foo", "bar"]], "foo:bar\n", 0],
+    ].each { |case_|
+      _run_kvseqTest(case_)
+    }
+  end
+
+  def _cleanSeq(seq)
+    # Create a new sequence by stripping whitespace from start and end
+    # of each value of each pair
+    seq.collect { |k, v| [k.strip(), v.strip()] }
+  end
+
+  def _run_kvseqTest(case_)
+    seq, kvform, warnings = case_
+
+    assert_log_line_count(warnings) {
+      # seq serializes to expected kvform
+      actual = Util.seq_to_kv(seq)
+
+      assert_equal(kvform, actual)
+      assert actual.is_a?(String)
+
+      # Strict mode should raise KVFormError instead of logging
+      # messages
+      if warnings > 0
+        assert_raise(KVFormError) do
+          Util.seq_to_kv(seq, true)
+        end
+      end
+
+      # Parse back to sequence. Expected to be unchanged, except
+      # stripping whitespace from start and end of values
+      # (i. e. ordering, case, and internal whitespace is preserved)
+      seq = Util.kv_to_seq(actual)
+      clean_seq = _cleanSeq(seq)
+
+      assert_equal(seq, clean_seq)
+    }
+  end
+
+  def test_kvexc
+    [
+     [["openid", "use\nful"]],
+     [["open\nid", "useful"]],
+     [["open\nid", "use\nful"]],
+     [["open:id", "useful"]],
+     [["foo", "bar"], ["ba\n d", "seed"]],
+     [["foo", "bar"], ["bad:", "seed"]],
+    ].each { |case_|
+      _run_kvexcTest(case_)
+    }
+  end
+
+  def _run_kvexcTest(case_)
+    seq = case_
+
+    assert_raise(KVFormError) do
+      Util.seq_to_kv(seq)
+    end
+  end
+
+  def test_convert
+    assert_log_line_count(2) {
+      result = Util.seq_to_kv([[1, 1]])
+      assert_equal(result, "1:1\n")
+    }
+  end
+end
diff --git a/vendor/gems/ruby-openid-2.1.4/test/test_kvpost.rb b/vendor/gems/ruby-openid-2.1.4/test/test_kvpost.rb
new file mode 100644 (file)
index 0000000..8f64804
--- /dev/null
@@ -0,0 +1,65 @@
+require "openid/kvpost"
+require "openid/kvform"
+require "openid/message"
+require "test/unit"
+require 'testutil'
+
+module OpenID
+  class KVPostTestCase < Test::Unit::TestCase
+    include FetcherMixin
+
+    def mk_resp(status, resp_hash)
+      return MockResponse.new(status, Util.dict_to_kv(resp_hash))
+    end
+
+    def test_msg_from_http_resp_success
+      resp = mk_resp(200, {'mode' => 'seitan'})
+      msg = Message.from_http_response(resp, 'http://invalid/')
+      assert_equal({'openid.mode' => 'seitan'}, msg.to_post_args)
+    end
+
+    def test_400
+      args = {'error' => 'I ate too much cheese',
+        'error_code' => 'sadness'}
+      resp = mk_resp(400, args)
+      begin
+        val = Message.from_http_response(resp, 'http://invalid/')
+      rescue ServerError => why
+        assert_equal(why.error_text, 'I ate too much cheese')
+        assert_equal(why.error_code, 'sadness')
+        assert_equal(why.message.to_args, args)
+      else
+        fail("Expected exception. Got: #{val}")
+      end
+    end
+
+    def test_500
+      args = {'error' => 'I ate too much cheese',
+        'error_code' => 'sadness'}
+      resp = mk_resp(500, args)
+      assert_raises(HTTPStatusError) {
+        Message.from_http_response(resp, 'http://invalid')
+      }
+    end
+
+    def make_kv_post_with_response(status, args)
+      resp = mk_resp(status, args)
+      mock_fetcher = Class.new do
+        define_method(:fetch) do |url, body, xxx, yyy|
+          resp
+        end
+      end
+      fetcher = mock_fetcher.new
+
+      with_fetcher(mock_fetcher.new) do
+        OpenID.make_kv_post(Message.from_openid_args(args), 'http://invalid/')
+      end
+    end
+
+    def test_make_kv_post
+      assert_raises(HTTPStatusError) {
+        make_kv_post_with_response(500, {})
+      }
+    end
+  end
+end
diff --git a/vendor/gems/ruby-openid-2.1.4/test/test_linkparse.rb b/vendor/gems/ruby-openid-2.1.4/test/test_linkparse.rb
new file mode 100644 (file)
index 0000000..6360d50
--- /dev/null
@@ -0,0 +1,101 @@
+require 'test/unit'
+require 'testutil'
+require 'openid/consumer/html_parse'
+
+class LinkParseTestCase < Test::Unit::TestCase
+  include OpenID::TestDataMixin
+
+  def attr_cmp(expected, found)
+    e = expected.to_a.sort
+    f = found.to_a.sort
+    while (ep = e.shift)
+      ek, ev = ep
+      fk, fv = f.shift
+      ok = false
+      while ek[-1] == '*'[0] # optional entry detected
+        if fk == ek[0...-1] and fv==ev # optional entry found
+          ok = true
+          break
+        else # not found. okay, move on to next expected pair
+          ek, ev = e.shift
+        end
+        if ek.nil?
+          if fk == nil
+            ok = true
+          end
+          break
+        end
+      end
+      next if ok
+      next if fk == ek and fv == ev
+      return false
+    end
+    return f.empty?
+  end
+
+  def test_attrcmp
+    good = [
+     [{'foo' => 'bar'},{'foo' => 'bar'}],
+     [{'foo*' => 'bar'},{'foo' => 'bar'}],
+     [{'foo' => 'bar', 'bam*' => 'baz'},{'foo' => 'bar'}],
+     [{'foo' => 'bar', 'bam*' => 'baz', 'tak' => 'tal'},
+        {'foo' => 'bar', 'tak' => 'tal'}],
+     ]
+    bad = [
+     [{},{'foo' => 'bar'}],
+     [{'foo' => 'bar'}, {'bam' => 'baz'}],
+     [{'foo' => 'bar'}, {}],
+     [{'foo*' => 'bar'},{'foo*' => 'bar'}],
+     [{'foo' => 'bar', 'tak' => 'tal'}, {'foo' => 'bar'}]
+    ]
+    good.each{|c|assert(attr_cmp(c[0],c[1]),c.inspect)}
+    bad.each{|c|assert(!attr_cmp(c[0],c[1]),c.inspect)}
+
+  end
+
+  def test_linkparse
+    cases = read_data_file('linkparse.txt', false).split("\n\n\n")
+
+    numtests = nil
+    testnum = 0
+    cases.each {|c|
+      headers, html = c.split("\n\n",2)
+      expected_links = []
+      name = ""
+      testnum += 1
+      headers.split("\n").each{|h|
+        k,v = h.split(":",2)
+        v = '' if v.nil?
+        if k == "Num Tests"
+          assert(numtests.nil?, "datafile parsing error: there can be only one NumTests")
+          numtests = v.to_i
+          testnum = 0
+          next
+        elsif k == "Name"
+          name = v.strip
+        elsif k == "Link" or k == "Link*"
+          attrs = {}
+          v.strip.split.each{|a|
+            kk,vv = a.split('=')
+            attrs[kk]=vv
+          }
+          expected_links << [k== "Link*", attrs]
+        else
+          assert(false, "datafile parsing error: bad header #{h}")
+        end
+      }
+      links = OpenID::parse_link_attrs(html)
+      
+      found = links.dup
+      expected = expected_links.dup
+      while(fl = found.shift)
+        optional, el = expected.shift
+        while optional and !attr_cmp(el, fl) and not expected.empty?
+          optional, el = expected.shift
+        end
+        assert(attr_cmp(el,fl), "#{name}: #{fl.inspect} does not match #{el.inspect}")
+      end
+    }
+    assert_equal(numtests, testnum, "Number of tests")
+  end
+end
diff --git a/vendor/gems/ruby-openid-2.1.4/test/test_message.rb b/vendor/gems/ruby-openid-2.1.4/test/test_message.rb
new file mode 100644 (file)
index 0000000..f8ef918
--- /dev/null
@@ -0,0 +1,1116 @@
+# last synced with Python openid.test.test_message on 6/29/2007.
+
+require 'test/unit'
+require 'util'
+require 'openid/message'
+
+require 'rexml/document'
+
+module OpenID
+  module GetArgsMixin
+
+    # Tests a standard set of behaviors of Message.get_arg with
+    # variations on handling defaults.
+    def get_arg_tests(ns, key, expected=nil)
+      assert_equal(expected, @m.get_arg(ns, key))
+
+      if expected.nil?
+        assert_equal(@m.get_arg(ns, key, :a_default), :a_default)
+        assert_raise(Message::KeyNotFound) { @m.get_arg(ns, key, NO_DEFAULT) }
+      else
+        assert_equal(@m.get_arg(ns, key, :a_default), expected)
+        assert_equal(@m.get_arg(ns, key, NO_DEFAULT), expected)
+      end
+
+    end
+
+  end
+
+  class EmptyMessageTestCase < Test::Unit::TestCase
+    include GetArgsMixin
+
+    def setup
+      @m = Message.new
+    end
+
+    def test_get_aliased_arg_no_default
+      assert_raises(Message::KeyNotFound) do
+        @m.get_aliased_arg('ns.pork', NO_DEFAULT)
+      end
+
+      ns_uri = "urn:pork"
+      @m.namespaces.add_alias(ns_uri, 'pork_alias')
+
+      # Should return ns_uri.
+      assert_equal(ns_uri, @m.get_aliased_arg('ns.pork_alias', NO_DEFAULT))
+    end
+
+    def test_to_post_args
+      assert_equal({}, @m.to_post_args)
+    end
+
+    def test_to_args
+      assert_equal({}, @m.to_args)
+    end
+
+    def test_to_kvform
+      assert_equal('', @m.to_kvform)
+    end
+
+    def test_from_kvform
+      kvform = "foo:bar\none:two\n"
+      args = {'foo' => 'bar', 'one' => 'two'}
+      expected_result = Message.from_openid_args(args)
+
+      assert_equal(expected_result, Message.from_kvform(kvform))
+    end
+
+    def test_to_url_encoded
+      assert_equal('', @m.to_url_encoded)
+    end
+
+    def test_to_url
+      base_url = 'http://base.url/'
+      assert_equal(base_url, @m.to_url(base_url))
+    end
+
+    def test_get_openid
+      assert_equal(nil, @m.get_openid_namespace)
+    end
+
+    def test_get_key_openid
+      assert_raise(UndefinedOpenIDNamespace) {
+        @m.get_key(OPENID_NS, nil)
+      }
+    end
+
+    def test_get_key_bare
+      assert_equal('foo', @m.get_key(BARE_NS, 'foo'))
+    end
+
+    def test_get_key_ns1
+      assert_equal(nil, @m.get_key(OPENID1_NS, 'foo'))
+    end
+
+    def test_get_key_ns2
+      assert_equal(nil, @m.get_key(OPENID2_NS, 'foo'))
+    end
+
+    def test_get_key_ns3
+      assert_equal(nil, @m.get_key('urn:something-special', 'foo'))
+    end
+
+    def test_has_key
+      assert_raise(UndefinedOpenIDNamespace) {
+        @m.has_key?(OPENID_NS, 'foo')
+      }
+    end
+
+    def test_has_key_bare
+      assert_equal(false, @m.has_key?(BARE_NS, 'foo'))
+    end
+
+    def test_has_key_ns1
+      assert_equal(false, @m.has_key?(OPENID1_NS, 'foo'))
+    end
+
+    def test_has_key_ns2
+      assert_equal(false, @m.has_key?(OPENID2_NS, 'foo'))
+    end
+
+    def test_has_key_ns3
+      assert_equal(false, @m.has_key?('urn:xxx', 'foo'))
+    end
+
+    def test_get_arg
+      assert_raise(UndefinedOpenIDNamespace) {
+        @m.get_args(OPENID_NS)
+      }
+    end
+
+    def test_get_arg_bare
+      get_arg_tests(ns=BARE_NS, key='foo')
+    end
+
+    def test_get_arg_ns1
+      get_arg_tests(ns=OPENID1_NS, key='foo')
+    end
+
+    def test_get_arg_ns2
+      get_arg_tests(ns=OPENID2_NS, key='foo')
+    end
+
+    def test_get_arg_ns3
+      get_arg_tests(ns='urn:nothing-significant', key='foo')
+    end
+
+    def test_get_args
+      assert_raise(UndefinedOpenIDNamespace) {
+        @m.get_args(OPENID_NS)
+      }
+    end
+
+    def test_get_args_bare
+      assert_equal({}, @m.get_args(BARE_NS))
+    end
+
+    def test_get_args_ns1
+      assert_equal({}, @m.get_args(OPENID1_NS))
+    end
+
+    def test_get_args_ns2
+      assert_equal({}, @m.get_args(OPENID2_NS))
+    end
+
+    def test_get_args_ns3
+      assert_equal({}, @m.get_args('urn:xxx'))
+    end
+
+    def test_update_args
+      assert_raise(UndefinedOpenIDNamespace) {
+        @m.update_args(OPENID_NS, {'does not'=>'matter'})
+      }
+    end
+
+    def _test_update_args_ns(ns)
+      updates = {
+        'camper van beethoven' => 'david l',
+        'magnolia electric, co' => 'jason m'
+      }
+      assert_equal({}, @m.get_args(ns))
+      @m.update_args(ns, updates)
+      assert_equal(updates, @m.get_args(ns))
+    end
+
+    def test_update_args_bare
+      _test_update_args_ns(BARE_NS)
+    end
+    def test_update_args_ns1
+      _test_update_args_ns(OPENID1_NS)
+    end
+    def test_update_args_ns2
+      _test_update_args_ns(OPENID2_NS)
+    end
+    def test_update_args_ns3
+      _test_update_args_ns('urn:xxx')
+    end
+
+    def test_set_arg
+      assert_raise(UndefinedOpenIDNamespace) {
+        @m.set_arg(OPENID_NS,'does not','matter')
+      }
+    end
+
+    def _test_set_arg_ns(ns)
+      key = 'Camper Van Beethoven'
+      value = 'David Lowery'
+      assert_equal(nil, @m.get_arg(ns, key))
+      @m.set_arg(ns, key, value)
+      assert_equal(value, @m.get_arg(ns, key))
+    end
+
+    def test_set_arg_bare
+      _test_set_arg_ns(BARE_NS)
+    end
+    def test_set_arg_ns1
+      _test_set_arg_ns(OPENID1_NS)
+    end
+    def test_set_arg_ns2
+      _test_set_arg_ns(OPENID2_NS)
+    end
+    def test_set_arg_ns3
+      _test_set_arg_ns('urn:xxx')
+    end
+
+    def test_del_arg
+      assert_raise(UndefinedOpenIDNamespace) {
+        @m.set_arg(OPENID_NS, 'does not', 'matter')
+      }
+    end
+
+    def _test_del_arg_ns(ns)
+      key = 'Fleeting Joys'
+      assert_equal(nil, @m.del_arg(ns, key))
+    end
+
+    def test_del_arg_bare
+      _test_del_arg_ns(BARE_NS)
+    end
+    def test_del_arg_ns1
+      _test_del_arg_ns(OPENID1_NS)
+    end
+    def test_del_arg_ns2
+      _test_del_arg_ns(OPENID2_NS)
+    end
+    def test_del_arg_ns3
+      _test_del_arg_ns('urn:xxx')
+    end
+
+    def test_isOpenID1
+      assert_equal(false, @m.is_openid1)
+    end
+
+    def test_isOpenID2
+      assert_equal(false, @m.is_openid2)
+    end
+
+    def test_set_openid_namespace
+      assert_raise(InvalidOpenIDNamespace) {
+        @m.set_openid_namespace('http://invalid/', false)
+      }
+    end
+  end
+
+  class OpenID1MessageTest < Test::Unit::TestCase
+    include GetArgsMixin
+
+    def setup
+      @m = Message.from_post_args({'openid.mode' => 'error',
+                                            'openid.error' => 'unit test'})
+    end
+
+    def test_has_openid_ns
+      assert_equal(OPENID1_NS, @m.get_openid_namespace)
+      assert_equal(OPENID1_NS,
+                   @m.namespaces.get_namespace_uri(NULL_NAMESPACE))
+    end
+
+    def test_get_aliased_arg
+      assert_equal('error', @m.get_aliased_arg('mode'))
+    end
+
+    def test_get_aliased_arg_ns
+      assert_equal(OPENID1_NS, @m.get_aliased_arg('ns'))
+    end
+
+    def test_get_aliased_arg_with_ns
+      @m = Message.from_post_args(
+          {'openid.mode' => 'error',
+           'openid.error' => 'unit test',
+           'openid.ns.invalid' => 'http://invalid/',
+           'openid.invalid.stuff' => 'things',
+           'openid.invalid.stuff.blinky' => 'powerplant',
+          })
+      assert_equal('http://invalid/', @m.get_aliased_arg('ns.invalid'))
+      assert_equal('things', @m.get_aliased_arg('invalid.stuff'))
+      assert_equal('powerplant', @m.get_aliased_arg('invalid.stuff.blinky'))
+    end
+
+    def test_get_aliased_arg_with_ns_default
+      @m = Message.from_post_args({})
+      assert_equal('monkeys!', @m.get_aliased_arg('ns.invalid',
+                                                  default="monkeys!"))
+    end
+
+    def test_to_post_args
+      assert_equal({'openid.mode' => 'error',
+                     'openid.error' => 'unit test'},
+                   @m.to_post_args)
+    end
+
+    def test_to_post_args_ns
+      invalid_ns = 'http://invalid/'
+      @m.namespaces.add_alias(invalid_ns, 'foos')
+      @m.set_arg(invalid_ns, 'ball', 'awesome')
+      @m.set_arg(BARE_NS, 'xey', 'value')
+      assert_equal({'openid.mode' => 'error',
+                     'openid.error' => 'unit test',
+                     'openid.foos.ball' => 'awesome',
+                     'xey' => 'value',
+                     'openid.ns.foos' => 'http://invalid/'
+                   }, @m.to_post_args)
+    end
+
+    def test_to_args
+      assert_equal({'mode' => 'error',
+                     'error' => 'unit test'},
+                   @m.to_args)
+    end
+
+    def test_to_kvform
+      assert_equal("error:unit test\nmode:error\n",
+                   @m.to_kvform)
+    end
+
+    def test_to_url_encoded
+      assert_equal('openid.error=unit+test&openid.mode=error',
+                   @m.to_url_encoded)
+    end
+
+    def test_to_url
+      base_url = 'http://base.url/'
+      actual = @m.to_url(base_url)
+      actual_base = actual[0...base_url.length]
+      assert_equal(base_url, actual_base)
+      assert_equal('?', actual[base_url.length].chr)
+      query = actual[base_url.length+1..-1]
+      assert_equal({'openid.mode'=>['error'],'openid.error'=>['unit test']},
+                   CGI.parse(query))
+    end
+
+    def test_get_openid
+      assert_equal(OPENID1_NS, @m.get_openid_namespace)
+    end
+
+    def test_get_key_openid
+      assert_equal('openid.mode', @m.get_key(OPENID_NS, 'mode'))
+    end
+
+    def test_get_key_bare
+      assert_equal('mode', @m.get_key(BARE_NS, 'mode'))
+    end
+
+    def test_get_key_ns1
+      assert_equal('openid.mode', @m.get_key(OPENID1_NS, 'mode'))
+    end
+
+    def test_get_key_ns2
+      assert_equal(nil, @m.get_key(OPENID2_NS, 'mode'))
+    end
+
+    def test_get_key_ns3
+      assert_equal(nil, @m.get_key('urn:xxx', 'mode'))
+    end
+
+    def test_has_key
+      assert_equal(true, @m.has_key?(OPENID_NS, 'mode'))
+    end
+    def test_has_key_bare
+      assert_equal(false, @m.has_key?(BARE_NS, 'mode'))
+    end
+    def test_has_key_ns1
+      assert_equal(true, @m.has_key?(OPENID1_NS, 'mode'))
+    end
+    def test_has_key_ns2
+      assert_equal(false, @m.has_key?(OPENID2_NS, 'mode'))
+    end
+    def test_has_key_ns3
+      assert_equal(false, @m.has_key?('urn:xxx', 'mode'))
+    end
+
+    def test_get_arg
+      assert_equal('error', @m.get_arg(OPENID_NS, 'mode'))
+    end
+
+    def test_get_arg_bare
+      get_arg_tests(ns=BARE_NS, key='mode')
+    end
+
+    def test_get_arg_ns
+      get_arg_tests(ns=OPENID_NS, key='mode', expected='error')
+    end
+
+    def test_get_arg_ns1
+      get_arg_tests(ns=OPENID1_NS, key='mode', expected='error')
+    end
+
+    def test_get_arg_ns2
+      get_arg_tests(ns=OPENID2_NS, key='mode')
+    end
+
+    def test_get_arg_ns3
+      get_arg_tests(ns='urn:nothing-significant', key='mode')
+    end
+
+    def test_get_args
+      assert_equal({'mode'=>'error','error'=>'unit test'},
+                   @m.get_args(OPENID_NS))
+    end
+    def test_get_args_bare
+      assert_equal({}, @m.get_args(BARE_NS))
+    end
+    def test_get_args_ns1
+      assert_equal({'mode'=>'error','error'=>'unit test'},
+                   @m.get_args(OPENID1_NS))
+    end
+    def test_get_args_ns2
+      assert_equal({}, @m.get_args(OPENID2_NS))
+    end
+    def test_get_args_ns3
+      assert_equal({}, @m.get_args('urn:xxx'))
+    end
+
+    def _test_update_args_ns(ns, before=nil)
+      if before.nil?
+        before = {}
+      end
+      update_args = {
+        'Camper van Beethoven'=>'David Lowery',
+        'Magnolia Electric Co.'=>'Jason Molina'
+      }
+      assert_equal(before, @m.get_args(ns))
+      @m.update_args(ns, update_args)
+      after = before.dup
+      after.update(update_args)
+      assert_equal(after, @m.get_args(ns))
+    end
+
+    def test_update_args
+      _test_update_args_ns(OPENID_NS, {'mode'=>'error','error'=>'unit test'})
+    end
+    def test_update_args_bare
+      _test_update_args_ns(BARE_NS)
+    end
+    def test_update_args_ns1
+      _test_update_args_ns(OPENID1_NS, {'mode'=>'error','error'=>'unit test'})
+    end
+    def test_update_args_ns2
+      _test_update_args_ns(OPENID2_NS)
+    end
+    def test_update_args_ns3
+      _test_update_args_ns('urn:xxx')
+    end
+
+    def _test_set_arg_ns(ns)
+      key = 'awesometown'
+      value = 'funny'
+      assert_equal(nil, @m.get_arg(ns,key))
+      @m.set_arg(ns, key, value)
+      assert_equal(value, @m.get_arg(ns,key))
+    end
+
+    def test_set_arg; _test_set_arg_ns(OPENID_NS); end
+    def test_set_arg_bare; _test_set_arg_ns(BARE_NS); end
+    def test_set_arg_ns1; _test_set_arg_ns(OPENID1_NS); end
+    def test_set_arg_ns2; _test_set_arg_ns(OPENID2_NS); end
+    def test_set_arg_ns3; _test_set_arg_ns('urn:xxx'); end
+
+    def _test_del_arg_ns(ns)
+      key = 'marry an'
+      value = 'ice cream sandwich'
+      @m.set_arg(ns, key, value)
+      assert_equal(value, @m.get_arg(ns,key))
+      @m.del_arg(ns,key)
+      assert_equal(nil, @m.get_arg(ns,key))
+    end
+
+    def test_del_arg; _test_del_arg_ns(OPENID_NS); end
+    def test_del_arg_bare; _test_del_arg_ns(BARE_NS); end
+    def test_del_arg_ns1; _test_del_arg_ns(OPENID1_NS); end
+    def test_del_arg_ns2; _test_del_arg_ns(OPENID2_NS); end
+    def test_del_arg_ns3; _test_del_arg_ns('urn:yyy'); end
+
+    def test_isOpenID1
+      assert_equal(true, @m.is_openid1)
+    end
+
+    def test_isOpenID2
+      assert_equal(false, @m.is_openid2)
+    end
+
+    def test_equal
+      assert_equal(Message.new, Message.new)
+      assert_not_equal(Message.new, nil)
+    end
+
+    def test_from_openid_args_undefined_ns
+      expected = 'almost.complete'
+      msg = Message.from_openid_args({'coverage.is' => expected})
+      actual = msg.get_arg(OPENID1_NS, 'coverage.is')
+      assert_equal(expected, actual)
+    end
+
+    # XXX: we need to implement the KVForm module before we can fix this
+    def TODOtest_from_kvform
+      kv = 'foos:ball\n'
+      msg = Message.from_kvform(kv)
+      assert_equal(msg.get(OPENID1_NS, 'foos'), 'ball')
+    end
+
+    def test_initialize_sets_namespace
+      msg = Message.new(OPENID1_NS)
+      assert_equal(OPENID1_NS, msg.get_openid_namespace)
+    end
+  end
+
+  class OpenID1ExplicitMessageTest < Test::Unit::TestCase
+    # XXX - check to make sure the test suite will get built the way this
+    # expects.
+    def setup
+      @m = Message.from_post_args({'openid.mode'=>'error',
+                                          'openid.error'=>'unit test',
+                                          'openid.ns'=>OPENID1_NS})
+    end
+
+    def test_to_post_args
+      assert_equal({'openid.mode' => 'error',
+                    'openid.error' => 'unit test',
+                    'openid.ns'=>OPENID1_NS,
+                    },
+                   @m.to_post_args)
+    end
+
+    def test_to_post_args_ns
+      invalid_ns = 'http://invalid/'
+      @m.namespaces.add_alias(invalid_ns, 'foos')
+      @m.set_arg(invalid_ns, 'ball', 'awesome')
+      @m.set_arg(BARE_NS, 'xey', 'value')
+      assert_equal({'openid.mode' => 'error',
+                     'openid.error' => 'unit test',
+                     'openid.foos.ball' => 'awesome',
+                     'xey' => 'value',
+                     'openid.ns'=>OPENID1_NS,
+                     'openid.ns.foos' => 'http://invalid/'
+                   }, @m.to_post_args)
+    end
+
+    def test_to_args
+      assert_equal({'mode' => 'error',
+                     'error' => 'unit test',
+                     'ns'=>OPENID1_NS
+                     },
+                   @m.to_args)
+    end
+
+    def test_to_kvform
+      assert_equal("error:unit test\nmode:error\nns:#{OPENID1_NS}\n",
+                   @m.to_kvform)
+    end
+
+    def test_to_url_encoded
+      assert_equal('openid.error=unit+test&openid.mode=error&openid.ns=http%3A%2F%2Fopenid.net%2Fsignon%2F1.0',
+                   @m.to_url_encoded)
+    end
+
+    def test_to_url
+      base_url = 'http://base.url/'
+      actual = @m.to_url(base_url)
+      actual_base = actual[0...base_url.length]
+      assert_equal(base_url, actual_base)
+      assert_equal('?', actual[base_url.length].chr)
+      query = actual[base_url.length+1..-1]
+      assert_equal({'openid.mode'=>['error'],
+                    'openid.error'=>['unit test'],
+                    'openid.ns'=>[OPENID1_NS],
+                    },
+                   CGI.parse(query))
+    end
+
+
+  end
+
+  class OpenID2MessageTest < Test::Unit::TestCase
+    include TestUtil
+
+    def setup
+      @m = Message.from_post_args({'openid.mode'=>'error',
+                                          'openid.error'=>'unit test',
+                                          'openid.ns'=>OPENID2_NS})
+      @m.set_arg(BARE_NS, 'xey', 'value')
+    end
+
+    def test_to_args_fails
+      assert_raises(ArgumentError) {
+        @m.to_args
+      }
+    end
+
+    def test_fix_ns_non_string
+      # Using has_key to invoke _fix_ns since _fix_ns should be private
+      assert_raises(ArgumentError) {
+        @m.has_key?(:non_string_namespace, "key")
+      }
+    end
+
+    def test_fix_ns_non_uri
+      # Using has_key to invoke _fix_ns since _fix_ns should be private
+      assert_log_matches(/identifiers SHOULD be URIs/) {
+        @m.has_key?("foo", "key")
+      }
+    end
+
+    def test_fix_ns_sreg_literal
+      # Using has_key to invoke _fix_ns since _fix_ns should be private
+      assert_log_matches(/identifiers SHOULD be URIs/, /instead of "sreg"/) {
+        @m.has_key?("sreg", "key")
+      }
+    end
+
+    def test_copy
+      n = @m.copy
+      assert_equal(@m, n)
+    end
+
+    def test_to_post_args
+      assert_equal({'openid.mode' => 'error',
+                     'openid.error' => 'unit test',
+                     'openid.ns' => OPENID2_NS,
+                     'xey' => 'value',
+                   }, @m.to_post_args)
+    end
+
+    def test_to_post_args_ns
+      invalid_ns = 'http://invalid/'
+      @m.namespaces.add_alias(invalid_ns, 'foos')
+      @m.set_arg(invalid_ns, 'ball', 'awesome')
+      assert_equal({'openid.mode' => 'error',
+                     'openid.error' => 'unit test',
+                     'openid.ns' => OPENID2_NS,
+                     'openid.ns.foos' => invalid_ns,
+                     'openid.foos.ball' => 'awesome',
+                     'xey' => 'value',
+                   }, @m.to_post_args)
+    end
+
+    def test_to_args
+      @m.del_arg(BARE_NS, 'xey')
+      assert_equal({'mode' => 'error',
+                   'error' => 'unit test',
+                   'ns' => OPENID2_NS},
+                   @m.to_args)
+    end
+
+    def test_to_kvform
+      @m.del_arg(BARE_NS, 'xey')
+      assert_equal("error:unit test\nmode:error\nns:#{OPENID2_NS}\n",
+                   @m.to_kvform)
+    end
+
+    def _test_urlencoded(s)
+      expected_list = ["openid.error=unit+test",
+                       "openid.mode=error",
+                       "openid.ns=#{CGI.escape(OPENID2_NS)}",
+                       "xey=value"]
+      # Hard to do this with string comparison since the mapping doesn't
+      # preserve order.
+      encoded_list = s.split('&')
+      encoded_list.sort!
+      assert_equal(expected_list, encoded_list)
+    end
+
+    def test_to_urlencoded
+      _test_urlencoded(@m.to_url_encoded)
+    end
+
+    def test_to_url
+      base_url = 'http://base.url/'
+      actual = @m.to_url(base_url)
+      actual_base = actual[0...base_url.length]
+      assert_equal(base_url, actual_base)
+      assert_equal('?', actual[base_url.length].chr)
+      query = actual[base_url.length+1..-1]
+      _test_urlencoded(query)
+    end
+
+    def test_get_openid
+      assert_equal(OPENID2_NS, @m.get_openid_namespace)
+    end
+
+    def test_get_key_openid
+      assert_equal('openid.mode', @m.get_key(OPENID2_NS, 'mode'))
+    end
+
+    def test_get_key_bare
+      assert_equal('mode', @m.get_key(BARE_NS, 'mode'))
+    end
+
+    def test_get_key_ns1
+      assert_equal(nil, @m.get_key(OPENID1_NS, 'mode'))
+    end
+
+    def test_get_key_ns2
+      assert_equal('openid.mode', @m.get_key(OPENID2_NS, 'mode'))
+    end
+
+    def test_get_key_ns3
+      assert_equal(nil, @m.get_key('urn:xxx', 'mode'))
+    end
+
+    def test_has_key_openid
+      assert_equal(true, @m.has_key?(OPENID_NS,'mode'))
+    end
+
+    def test_has_key_bare
+      assert_equal(false, @m.has_key?(BARE_NS,'mode'))
+    end
+
+    def test_has_key_ns1
+      assert_equal(false, @m.has_key?(OPENID1_NS,'mode'))
+    end
+
+    def test_has_key_ns2
+      assert_equal(true, @m.has_key?(OPENID2_NS,'mode'))
+    end
+
+    def test_has_key_ns3
+      assert_equal(false, @m.has_key?('urn:xxx','mode'))
+    end
+
+    # XXX - getArgTest
+    def test_get_arg_openid
+      assert_equal('error', @m.get_arg(OPENID_NS,'mode'))
+    end
+
+    def test_get_arg_bare
+      assert_equal(nil, @m.get_arg(BARE_NS,'mode'))
+    end
+
+    def test_get_arg_ns1
+      assert_equal(nil, @m.get_arg(OPENID1_NS,'mode'))
+    end
+
+    def test_get_arg_ns2
+      assert_equal('error', @m.get_arg(OPENID2_NS,'mode'))
+    end
+
+    def test_get_arg_ns3
+      assert_equal(nil, @m.get_arg('urn:bananastand','mode'))
+    end
+
+    def test_get_args_openid
+      assert_equal({'mode'=>'error','error'=>'unit test'},
+                   @m.get_args(OPENID_NS))
+    end
+
+    def test_get_args_bare
+      assert_equal({'xey'=>'value'},
+                   @m.get_args(BARE_NS))
+    end
+
+    def test_get_args_ns1
+      assert_equal({},
+                   @m.get_args(OPENID1_NS))
+    end
+
+    def test_get_args_ns2
+      assert_equal({'mode'=>'error','error'=>'unit test'},
+                   @m.get_args(OPENID2_NS))
+    end
+
+    def test_get_args_ns3
+      assert_equal({},
+                   @m.get_args('urn:loose seal'))
+    end
+
+    def _test_update_args_ns(ns, before=nil)
+      before = {} unless before
+      update_args = {'aa'=>'bb','cc'=>'dd'}
+
+      assert_equal(before, @m.get_args(ns))
+      @m.update_args(ns, update_args)
+      after = before.dup
+      after.update(update_args)
+      assert_equal(after, @m.get_args(ns))
+    end
+
+    def test_update_args_openid
+      _test_update_args_ns(OPENID_NS, {'mode'=>'error','error'=>'unit test'})
+    end
+
+    def test_update_args_bare
+      _test_update_args_ns(BARE_NS, {'xey'=>'value'})
+    end
+
+    def test_update_args_ns1
+      _test_update_args_ns(OPENID1_NS)
+    end
+
+    def test_update_args_ns2
+      _test_update_args_ns(OPENID2_NS, {'mode'=>'error','error'=>'unit test'})
+    end
+
+    def test_update_args_ns3
+      _test_update_args_ns('urn:sven')
+    end
+
+    def _test_set_arg_ns(ns)
+      key = "logan's"
+      value = "run"
+      assert_equal(nil, @m.get_arg(ns,key))
+      @m.set_arg(ns, key, value)
+      assert_equal(value, @m.get_arg(ns,key))
+    end
+
+    def test_set_arg_openid; _test_set_arg_ns(OPENID_NS); end
+    def test_set_arg_bare; _test_set_arg_ns(BARE_NS); end
+    def test_set_arg_ns1; _test_set_arg_ns(OPENID1_NS); end
+    def test_set_arg_ns2; _test_set_arg_ns(OPENID2_NS); end
+    def test_set_arg_ns3; _test_set_arg_ns('urn:g'); end
+
+    def test_bad_alias
+      # Make sure dotted aliases and OpenID protocol fields are not allowed
+      # as namespace aliases.
+
+      fields = OPENID_PROTOCOL_FIELDS + ['dotted.alias']
+
+      fields.each { |f|
+        args = {"openid.ns.#{f}" => "blah#{f}",
+          "openid.#{f}.foo" => "test#{f}"}
+
+        # .fromPostArgs covers .fromPostArgs, .fromOpenIDArgs,
+        # ._fromOpenIDArgs, and .fromOpenIDArgs (since it calls
+        # .fromPostArgs).
+        assert_raise(AssertionError) {
+          Message.from_post_args(args)
+        }
+      }
+    end
+
+    def test_from_post_args
+      msg = Message.from_post_args({'foos' => 'ball'})
+      assert_equal('ball', msg.get_arg(BARE_NS, 'foos'))
+    end
+
+    def _test_del_arg_ns(ns)
+      key = 'no'
+      value = 'socks'
+      assert_equal(nil, @m.get_arg(ns, key))
+      @m.set_arg(ns, key, value)
+      assert_equal(value, @m.get_arg(ns, key))
+      @m.del_arg(ns, key)
+      assert_equal(nil, @m.get_arg(ns, key))
+    end
+
+    def test_del_arg_openid; _test_del_arg_ns(OPENID_NS); end
+    def test_del_arg_bare; _test_del_arg_ns(BARE_NS); end
+    def test_del_arg_ns1; _test_del_arg_ns(OPENID1_NS); end
+    def test_del_arg_ns2; _test_del_arg_ns(OPENID2_NS); end
+    def test_del_arg_ns3; _test_del_arg_ns('urn:tofu'); end
+
+    def test_overwrite_extension_arg
+      ns = 'urn:unittest_extension'
+      key = 'mykey'
+      value_1 = 'value_1'
+      value_2 = 'value_2'
+
+      @m.set_arg(ns, key, value_1)
+      assert_equal(value_1, @m.get_arg(ns, key))
+      @m.set_arg(ns, key, value_2)
+      assert_equal(value_2, @m.get_arg(ns, key))
+    end
+
+    def test_argList
+      assert_raise(ArgumentError) {
+        Message.from_post_args({'arg' => [1, 2, 3]})
+      }
+    end
+
+    def test_isOpenID1
+      assert_equal(false, @m.is_openid1)
+    end
+
+    def test_isOpenID2
+      assert_equal(true, @m.is_openid2)
+    end
+  end
+
+  class MessageTest < Test::Unit::TestCase
+    def setup
+      @postargs = {
+        'openid.ns' => OPENID2_NS,
+        'openid.mode' => 'checkid_setup',
+        'openid.identity' => 'http://bogus.example.invalid:port/',
+        'openid.assoc_handle' => 'FLUB',
+        'openid.return_to' => 'Neverland',
+      }
+
+      @action_url = 'scheme://host:port/path?query'
+
+      @form_tag_attrs = {
+        'company' => 'janrain',
+        'class' => 'fancyCSS',
+      }
+
+      @submit_text = 'GO!'
+
+      ### Expected data regardless of input
+
+      @required_form_attrs = {
+        'accept-charset' => 'UTF-8',
+        'enctype' => 'application/x-www-form-urlencoded',
+        'method' => 'post',
+      }
+    end
+
+    def _checkForm(html, message_, action_url,
+                   form_tag_attrs, submit_text)
+      @xml = REXML::Document.new(html)
+
+      # Get root element
+      form = @xml.root
+
+      # Check required form attributes
+      @required_form_attrs.each { |k, v|
+        assert(form.attributes[k] == v,
+               "Expected '#{v}' for required form attribute '#{k}', got '#{form.attributes[k]}'")
+      }
+
+      # Check extra form attributes
+      @form_tag_attrs.each { |k, v|
+        # Skip attributes that already passed the required attribute
+        # check, since they should be ignored by the form generation
+        # code.
+        if @required_form_attrs.include?(k)
+          continue
+        end
+
+        assert(form.attributes[k] == v,
+               "Form attribute '#{k}' should be '#{v}', found '#{form.attributes[k]}'")
+
+        # Check hidden fields against post args
+        hiddens = []
+        form.each { |e|
+          if (e.is_a?(REXML::Element)) and
+              (e.name.upcase() == 'INPUT') and
+              (e.attributes['type'].upcase() == 'HIDDEN')
+            # For each post arg, make sure there is a hidden with that
+            # value.  Make sure there are no other hiddens.
+            hiddens += [e]
+          end
+        }
+
+        message_.to_post_args().each { |name, value|
+          success = false
+
+          hiddens.each { |e|
+            if e.attributes['name'] == name
+              assert(e.attributes['value'] == value,
+                     "Expected value of hidden input '#{e.attributes['name']}' " +
+                     "to be '#{value}', got '#{e.attributes['value']}'")
+              success = true
+              break
+            end
+          }
+
+          if !success
+            flunk "Post arg '#{name}' not found in form"
+          end
+        }
+
+        hiddens.each { |e|
+          assert(message_.to_post_args().keys().include?(e.attributes['name']),
+                 "Form element for '#{e.attributes['name']}' not in " +
+                 "original message")
+        }
+
+        # Check action URL
+        assert(form.attributes['action'] == action_url,
+               "Expected form 'action' to be '#{action_url}', got '#{form.attributes['action']}'")
+
+        # Check submit text
+        submits = []
+        form.each { |e|
+          if (e.is_a?(REXML::Element)) and
+              (e.name.upcase() == 'INPUT') and
+              e.attributes['type'].upcase() == 'SUBMIT'
+            submits += [e]
+          end
+        }
+
+        assert(submits.length == 1,
+               "Expected only one 'input' with type = 'submit', got #{submits.length}")
+
+        assert(submits[0].attributes['value'] == submit_text,
+               "Expected submit value to be '#{submit_text}', " +
+               "got '#{submits[0].attributes['value']}'")
+      }
+
+    end
+
+    def test_toFormMarkup
+      m = Message.from_post_args(@postargs)
+      html = m.to_form_markup(@action_url, @form_tag_attrs,
+                              @submit_text)
+      _checkForm(html, m, @action_url,
+                 @form_tag_attrs, @submit_text)
+    end
+
+    def test_overrideMethod
+      # Be sure that caller cannot change form method to GET.
+      m = Message.from_post_args(@postargs)
+
+      tag_attrs = @form_tag_attrs.clone
+      tag_attrs['method'] = 'GET'
+
+      html = m.to_form_markup(@action_url, @form_tag_attrs,
+                              @submit_text)
+      _checkForm(html, m, @action_url,
+                 @form_tag_attrs, @submit_text)
+    end
+
+    def test_overrideRequired
+      # Be sure that caller CANNOT change the form charset for
+      # encoding type.
+      m = Message.from_post_args(@postargs)
+
+      tag_attrs = @form_tag_attrs.clone
+      tag_attrs['accept-charset'] = 'UCS4'
+      tag_attrs['enctype'] = 'invalid/x-broken'
+
+      html = m.to_form_markup(@action_url, tag_attrs,
+                              @submit_text)
+      _checkForm(html, m, @action_url,
+                 tag_attrs, @submit_text)
+    end
+  end
+
+  class NamespaceMapTestCase < Test::Unit::TestCase
+
+    def test_onealias
+      nsm = NamespaceMap.new
+      uri = 'http://example.com/foo'
+      _alias = 'foo'
+      nsm.add_alias(uri, _alias)
+      assert_equal(uri, nsm.get_namespace_uri(_alias))
+      assert_equal(_alias, nsm.get_alias(uri))
+    end
+
+
+    def test_iteration
+      nsm = NamespaceMap.new
+      uripat = "http://example.com/foo%i"
+      nsm.add(uripat % 0)
+
+      (1..23).each { |i|
+        assert_equal(false, nsm.member?(uripat % i))
+        nsm.add(uripat % i)
+      }
+      nsm.each { |uri, _alias|
+        assert_equal(uri[22..-1], _alias[3..-1])
+      }
+
+      nsm = NamespaceMap.new
+      alias_ = 'bogus'
+      uri = 'urn:bogus'
+
+      nsm.add_alias(uri, alias_)
+
+      assert_equal(nsm.aliases(), [alias_])
+      assert_equal(nsm.namespace_uris(), [uri])
+    end
+
+    def test_register_default_alias
+      invalid_ns = 'http://invalid/'
+      alias_ = 'invalid'
+      Message.register_namespace_alias(invalid_ns, alias_)
+      # Doing it again doesn't raise an exception
+      Message.register_namespace_alias(invalid_ns, alias_)
+
+      # Once it's registered, you can't register it again
+      assert_raises(NamespaceAliasRegistrationError) {
+        Message.register_namespace_alias(invalid_ns, 'another_alias')
+      }
+
+      # Once it's registered, you can't register another URL with that alias
+      assert_raises(NamespaceAliasRegistrationError) {
+        Message.register_namespace_alias('http://janrain.com/', alias_)
+      }
+
+      # It gets used automatically by the Message class:
+      msg = Message.from_openid_args({'invalid.stuff' => 'things'})
+      assert(msg.is_openid1)
+      assert_equal(alias_, msg.namespaces.get_alias(invalid_ns))
+      assert_equal(invalid_ns, msg.namespaces.get_namespace_uri(alias_))
+    end
+
+    def test_alias_defined_twice
+      nsm = NamespaceMap.new
+      uri = 'urn:bogus'
+
+      nsm.add_alias(uri, 'foos')
+      assert_raises(IndexError) {
+        nsm.add_alias(uri, 'ball')
+      }
+    end
+  end
+end
diff --git a/vendor/gems/ruby-openid-2.1.4/test/test_nonce.rb b/vendor/gems/ruby-openid-2.1.4/test/test_nonce.rb
new file mode 100644 (file)
index 0000000..b8a1a05
--- /dev/null
@@ -0,0 +1,89 @@
+require 'test/unit'
+require 'openid/store/nonce'
+
+module OpenID
+  class NonceTestCase < Test::Unit::TestCase
+
+     NONCE_RE = /\A\d{4}-\d\d-\d\dT\d\d:\d\d:\d\dZ/
+
+    def test_mk_nonce
+      nonce = Nonce::mk_nonce
+      assert(nonce.match(NONCE_RE))
+      assert(nonce.size == 26)
+    end
+
+    def test_mk_nonce_time
+      nonce = Nonce::mk_nonce(0)
+      assert(nonce.match(NONCE_RE))
+      assert(nonce.size == 26)
+      assert(nonce.match(/^1970-01-01T00:00:00Z/))
+    end
+
+    def test_split
+      s = '1970-01-01T00:00:00Z'
+      expected_t = 0
+      expected_salt = ''
+      actual_t, actual_salt = Nonce::split_nonce(s)
+      assert_equal(expected_t, actual_t)
+      assert_equal(expected_salt, actual_salt)
+    end
+
+    def test_mk_split
+      t = 42
+      nonce_str = Nonce::mk_nonce(t)
+      assert(nonce_str.match(NONCE_RE))
+      at, salt = Nonce::split_nonce(nonce_str)
+      assert_equal(6, salt.size)
+      assert_equal(t, at)
+    end
+
+    def test_bad_split
+      cases = [
+        '',
+        '1970-01-01T00:00:00+1:00',
+        '1969-01-01T00:00:00Z',
+        '1970-00-01T00:00:00Z',
+        '1970.01-01T00:00:00Z',
+        'Thu Sep  7 13:29:31 PDT 2006',
+        'monkeys',
+        ]
+      cases.each{|c|
+        assert_raises(ArgumentError, c.inspect) { Nonce::split_nonce(c) }
+      }
+    end
+
+    def test_check_timestamp
+      cases = [
+        # exact, no allowed skew
+        ['1970-01-01T00:00:00Z', 0, 0, true],
+
+        # exact, large skew
+        ['1970-01-01T00:00:00Z', 1000, 0, true],
+
+        # no allowed skew, one second old
+        ['1970-01-01T00:00:00Z', 0, 1, false],
+
+        # many seconds old, outside of skew
+        ['1970-01-01T00:00:00Z', 10, 50, false],
+
+        # one second old, one second skew allowed
+        ['1970-01-01T00:00:00Z', 1, 1, true],
+
+        # One second in the future, one second skew allowed
+        ['1970-01-01T00:00:02Z', 1, 1, true],
+
+        # two seconds in the future, one second skew allowed
+        ['1970-01-01T00:00:02Z', 1, 0, false],
+
+        # malformed nonce string
+        ['monkeys', 0, 0, false],
+      ]
+      
+      cases.each{|c|
+        (nonce_str, allowed_skew, now, expected) = c
+        actual = Nonce::check_timestamp(nonce_str, allowed_skew, now)
+        assert_equal(expected, actual, c.inspect)
+      }
+    end
+  end
+end
diff --git a/vendor/gems/ruby-openid-2.1.4/test/test_openid_yadis.rb b/vendor/gems/ruby-openid-2.1.4/test/test_openid_yadis.rb
new file mode 100644 (file)
index 0000000..fb8c71e
--- /dev/null
@@ -0,0 +1,178 @@
+
+require 'test/unit'
+require 'openid/consumer/discovery'
+require 'openid/yadis/services'
+
+module OpenID
+
+  XRDS_BOILERPLATE = <<EOF
+<?xml version="1.0" encoding="UTF-8"?>
+<xrds:XRDS xmlns:xrds="xri://$xrds"
+           xmlns="xri://$xrd*($v*2.0)"
+           xmlns:openid="http://openid.net/xmlns/1.0">
+    <XRD>
+%s
+    </XRD>
+</xrds:XRDS>
+EOF
+
+  def self.mkXRDS(services)
+    return sprintf(XRDS_BOILERPLATE, services)
+  end
+
+  def self.mkService(uris=nil, type_uris=nil, local_id=nil, dent="        ")
+    chunks = [dent, "<Service>\n"]
+    dent2 = dent + "    "
+    if type_uris
+      type_uris.each { |type_uri|
+        chunks += [dent2 + "<Type>", type_uri, "</Type>\n"]
+      }
+    end
+
+    if uris
+      uris.each { |uri|
+        if uri.is_a?(Array)
+          uri, prio = uri
+        else
+          prio = nil
+        end
+
+        chunks += [dent2, "<URI"]
+        if !prio.nil?
+          chunks += [" priority='", str(prio), "'"]
+        end
+        chunks += [">", uri, "</URI>\n"]
+      }
+    end
+
+    if local_id
+      chunks += [dent2, "<openid:Delegate>", local_id, "</openid:Delegate>\n"]
+    end
+
+    chunks += [dent, "</Service>\n"]
+
+    return chunks.join("")
+  end
+
+  # Different sets of server URLs for use in the URI tag
+  SERVER_URL_OPTIONS = [
+                        [], # This case should not generate an endpoint object
+                        ['http://server.url/'],
+                        ['https://server.url/'],
+                        ['https://server.url/', 'http://server.url/'],
+                        ['https://server.url/',
+                         'http://server.url/',
+                         'http://example.server.url/'],
+                       ]
+
+  # Used for generating test data
+  def OpenID.subsets(l)
+    subsets_list = [[]]
+    l.each { |x|
+      subsets_list += subsets_list.collect { |t| [x] + t }
+    }
+
+    return subsets_list
+  end
+
+  # A couple of example extension type URIs. These are not at all
+  # official, but are just here for testing.
+  EXT_TYPES = [
+               'http://janrain.com/extension/blah',
+               'http://openid.net/sreg/1.0',
+              ]
+
+  # Range of valid Delegate tag values for generating test data
+  LOCAL_ID_OPTIONS = [
+                      nil,
+                      'http://vanity.domain/',
+                      'https://somewhere/yadis/',
+                     ]
+
+  class OpenIDYadisTest
+    def initialize(uris, type_uris, local_id)
+      super()
+      @uris = uris
+      @type_uris = type_uris
+      @local_id = local_id
+
+      @yadis_url = 'http://unit.test/'
+
+      # Create an XRDS document to parse
+      services = OpenID.mkService(@uris,
+                                  @type_uris,
+                                  @local_id)
+      @xrds = OpenID.mkXRDS(services)
+    end
+
+    def runTest(testcase)
+      # Parse into endpoint objects that we will check
+      endpoints = Yadis.apply_filter(@yadis_url, @xrds, OpenIDServiceEndpoint)
+
+      # make sure there are the same number of endpoints as URIs. This
+      # assumes that the type_uris contains at least one OpenID type.
+      testcase.assert_equal(@uris.length, endpoints.length)
+
+      # So that we can check equality on the endpoint types
+      type_uris = @type_uris.dup
+      type_uris.sort!
+
+      seen_uris = []
+      endpoints.each { |endpoint|
+        seen_uris << endpoint.server_url
+
+        # All endpoints will have same yadis_url
+        testcase.assert_equal(@yadis_url, endpoint.claimed_id)
+
+        # and local_id
+        testcase.assert_equal(@local_id, endpoint.local_id)
+
+        # and types
+        actual_types = endpoint.type_uris.dup
+        actual_types.sort!
+        testcase.assert_equal(type_uris, actual_types, actual_types.inspect)
+      }
+
+      # So that they will compare equal, because we don't care what
+      # order they are in
+      seen_uris.sort!
+      uris = @uris.dup
+      uris.sort!
+
+      # Make sure we saw all URIs, and saw each one once
+      testcase.assert_equal(uris, seen_uris)
+    end
+  end
+
+  class OpenIDYadisTests < Test::Unit::TestCase
+    def test_openid_yadis
+      data = []
+
+      # All valid combinations of Type tags that should produce an
+      # OpenID endpoint
+      type_uri_options = []
+
+      OpenID.subsets([OPENID_1_0_TYPE, OPENID_1_1_TYPE]).each { |ts|
+        OpenID.subsets(EXT_TYPES).each { |exts|
+          if !ts.empty?
+            type_uri_options << exts + ts
+          end
+        }
+      }
+
+      # All combinations of valid URIs, Type URIs and Delegate tags
+      SERVER_URL_OPTIONS.each { |uris|
+        type_uri_options.each { |type_uris|
+          LOCAL_ID_OPTIONS.each { |local_id|
+            data << [uris, type_uris, local_id]
+          }
+        }
+      }
+
+      data.each { |args|
+        t = OpenIDYadisTest.new(*args)
+        t.runTest(self)
+      }
+    end
+  end
+end
diff --git a/vendor/gems/ruby-openid-2.1.4/test/test_pape.rb b/vendor/gems/ruby-openid-2.1.4/test/test_pape.rb
new file mode 100644 (file)
index 0000000..bd8289c
--- /dev/null
@@ -0,0 +1,247 @@
+require 'openid/extensions/pape'
+require 'openid/message'
+require 'openid/server'
+require 'openid/consumer/responses'
+
+module OpenID
+  module PAPETest
+    class PapeRequestTestCase < Test::Unit::TestCase
+      def setup
+        @req = PAPE::Request.new
+      end
+
+      def test_construct
+        assert_equal([], @req.preferred_auth_policies)
+        assert_equal(nil, @req.max_auth_age)
+        assert_equal('pape', @req.ns_alias)
+
+        req2 = PAPE::Request.new([PAPE::AUTH_MULTI_FACTOR], 1000)
+        assert_equal([PAPE::AUTH_MULTI_FACTOR], req2.preferred_auth_policies)
+        assert_equal(1000, req2.max_auth_age)
+      end
+
+      def test_add_policy_uri
+        assert_equal([], @req.preferred_auth_policies)
+        @req.add_policy_uri(PAPE::AUTH_MULTI_FACTOR)
+        assert_equal([PAPE::AUTH_MULTI_FACTOR], @req.preferred_auth_policies)
+        @req.add_policy_uri(PAPE::AUTH_MULTI_FACTOR)
+        assert_equal([PAPE::AUTH_MULTI_FACTOR], @req.preferred_auth_policies)
+        @req.add_policy_uri(PAPE::AUTH_PHISHING_RESISTANT)
+        assert_equal([PAPE::AUTH_MULTI_FACTOR, PAPE::AUTH_PHISHING_RESISTANT], @req.preferred_auth_policies)
+        @req.add_policy_uri(PAPE::AUTH_MULTI_FACTOR)
+        assert_equal([PAPE::AUTH_MULTI_FACTOR, PAPE::AUTH_PHISHING_RESISTANT], @req.preferred_auth_policies)
+      end
+
+      def test_get_extension_args
+        assert_equal({'preferred_auth_policies' => ''}, @req.get_extension_args)
+        @req.add_policy_uri('http://uri')
+        assert_equal({'preferred_auth_policies' => 'http://uri'}, @req.get_extension_args)
+        @req.add_policy_uri('http://zig')
+        assert_equal({'preferred_auth_policies' => 'http://uri http://zig'}, @req.get_extension_args)
+        @req.max_auth_age = 789
+        assert_equal({'preferred_auth_policies' => 'http://uri http://zig', 'max_auth_age' => '789'}, @req.get_extension_args)
+      end
+
+      def test_parse_extension_args
+        args = {'preferred_auth_policies' => 'http://foo http://bar',
+                'max_auth_age' => '9'}
+        @req.parse_extension_args(args)
+        assert_equal(9, @req.max_auth_age)
+        assert_equal(['http://foo','http://bar'], @req.preferred_auth_policies)
+      end
+
+      def test_parse_extension_args_empty
+        @req.parse_extension_args({})
+        assert_equal(nil, @req.max_auth_age)
+        assert_equal([], @req.preferred_auth_policies)
+      end
+
+      def test_from_openid_request
+        openid_req_msg = Message.from_openid_args({
+          'mode' => 'checkid_setup',
+          'ns' => OPENID2_NS,
+          'ns.pape' => PAPE::NS_URI,
+          'pape.preferred_auth_policies' => [PAPE::AUTH_MULTI_FACTOR, PAPE::AUTH_PHISHING_RESISTANT].join(' '),
+          'pape.max_auth_age' => '5476'
+          })
+        oid_req = Server::OpenIDRequest.new
+        oid_req.message = openid_req_msg
+        req = PAPE::Request.from_openid_request(oid_req)
+        assert_equal([PAPE::AUTH_MULTI_FACTOR, PAPE::AUTH_PHISHING_RESISTANT], req.preferred_auth_policies)
+        assert_equal(5476, req.max_auth_age)
+      end
+
+      def test_from_openid_request_no_pape
+        message = Message.new
+        openid_req = Server::OpenIDRequest.new
+        openid_req.message = message
+        pape_req = PAPE::Request.from_openid_request(openid_req)
+        assert(pape_req.nil?)
+      end
+
+      def test_preferred_types
+        @req.add_policy_uri(PAPE::AUTH_PHISHING_RESISTANT)
+        @req.add_policy_uri(PAPE::AUTH_MULTI_FACTOR)
+        pt = @req.preferred_types([PAPE::AUTH_MULTI_FACTOR,
+                                   PAPE::AUTH_MULTI_FACTOR_PHYSICAL])
+        assert_equal([PAPE::AUTH_MULTI_FACTOR], pt)
+      end
+    end
+
+    class DummySuccessResponse
+      attr_accessor :message
+
+      def initialize(message, signed_stuff)
+        @message = message
+        @signed_stuff = signed_stuff
+      end
+
+      def get_signed_ns(ns_uri)
+        return @signed_stuff
+      end
+
+    end
+
+    class PapeResponseTestCase < Test::Unit::TestCase
+      def setup
+        @req = PAPE::Response.new
+      end
+
+      def test_construct
+        assert_equal([], @req.auth_policies)
+        assert_equal(nil, @req.auth_time)
+        assert_equal('pape', @req.ns_alias)
+        assert_equal(nil, @req.nist_auth_level)
+
+        req2 = PAPE::Response.new([PAPE::AUTH_MULTI_FACTOR], "1983-11-05T12:30:24Z", 3)
+        assert_equal([PAPE::AUTH_MULTI_FACTOR], req2.auth_policies)
+        assert_equal("1983-11-05T12:30:24Z", req2.auth_time)
+        assert_equal(3, req2.nist_auth_level)
+      end
+
+      def test_add_policy_uri
+        assert_equal([], @req.auth_policies)
+        @req.add_policy_uri(PAPE::AUTH_MULTI_FACTOR)
+        assert_equal([PAPE::AUTH_MULTI_FACTOR], @req.auth_policies)
+        @req.add_policy_uri(PAPE::AUTH_MULTI_FACTOR)
+        assert_equal([PAPE::AUTH_MULTI_FACTOR], @req.auth_policies)
+        @req.add_policy_uri(PAPE::AUTH_PHISHING_RESISTANT)
+        assert_equal([PAPE::AUTH_MULTI_FACTOR, PAPE::AUTH_PHISHING_RESISTANT], @req.auth_policies)
+        @req.add_policy_uri(PAPE::AUTH_MULTI_FACTOR)
+        assert_equal([PAPE::AUTH_MULTI_FACTOR, PAPE::AUTH_PHISHING_RESISTANT], @req.auth_policies)
+      end
+
+      def test_get_extension_args
+        assert_equal({'auth_policies' => 'none'}, @req.get_extension_args)
+        @req.add_policy_uri('http://uri')
+        assert_equal({'auth_policies' => 'http://uri'}, @req.get_extension_args)
+        @req.add_policy_uri('http://zig')
+        assert_equal({'auth_policies' => 'http://uri http://zig'}, @req.get_extension_args)
+        @req.auth_time =  "1983-11-05T12:30:24Z"
+        assert_equal({'auth_policies' => 'http://uri http://zig', 'auth_time' => "1983-11-05T12:30:24Z"}, @req.get_extension_args)
+        @req.nist_auth_level = 3
+        assert_equal({'auth_policies' => 'http://uri http://zig', 'auth_time' => "1983-11-05T12:30:24Z", 'nist_auth_level' => '3'}, @req.get_extension_args)
+      end
+
+      def test_get_extension_args_error_auth_age
+        @req.auth_time = "the beginning of time"
+        assert_raises(ArgumentError) { @req.get_extension_args }
+      end
+
+      def test_get_extension_args_error_nist_auth_level
+        @req.nist_auth_level = "high as a kite"
+        assert_raises(ArgumentError) { @req.get_extension_args }
+        @req.nist_auth_level = 5
+        assert_raises(ArgumentError) { @req.get_extension_args }
+        @req.nist_auth_level = -1
+        assert_raises(ArgumentError) { @req.get_extension_args }
+      end
+
+      def test_parse_extension_args
+        args = {'auth_policies' => 'http://foo http://bar',
+                'auth_time' => '1983-11-05T12:30:24Z'}
+        @req.parse_extension_args(args)
+        assert_equal('1983-11-05T12:30:24Z', @req.auth_time)
+        assert_equal(['http://foo','http://bar'], @req.auth_policies)
+      end
+
+      def test_parse_extension_args_empty
+        @req.parse_extension_args({})
+        assert_equal(nil, @req.auth_time)
+        assert_equal([], @req.auth_policies)
+      end
+      
+      def test_parse_extension_args_strict_bogus1
+        args = {'auth_policies' => 'http://foo http://bar',
+                'auth_time' => 'this one time'}
+        assert_raises(ArgumentError) { 
+          @req.parse_extension_args(args, true)
+        }
+      end
+
+      def test_parse_extension_args_strict_bogus2
+        args = {'auth_policies' => 'http://foo http://bar',
+                'auth_time' => '1983-11-05T12:30:24Z',
+                'nist_auth_level' => 'some'}
+        assert_raises(ArgumentError) { 
+          @req.parse_extension_args(args, true)
+        }
+      end
+      
+      def test_parse_extension_args_strict_good
+        args = {'auth_policies' => 'http://foo http://bar',
+                'auth_time' => '2007-10-11T05:25:18Z',
+                'nist_auth_level' => '0'}
+        @req.parse_extension_args(args, true)
+        assert_equal(['http://foo','http://bar'], @req.auth_policies)
+        assert_equal('2007-10-11T05:25:18Z', @req.auth_time)
+        assert_equal(0, @req.nist_auth_level)
+      end
+
+      def test_parse_extension_args_nostrict_bogus
+        args = {'auth_policies' => 'http://foo http://bar',
+                'auth_time' => 'some time ago',
+                'nist_auth_level' => 'some'}
+        @req.parse_extension_args(args)
+        assert_equal(['http://foo','http://bar'], @req.auth_policies)
+        assert_equal(nil, @req.auth_time)
+        assert_equal(nil, @req.nist_auth_level)
+      end
+
+      
+      def test_from_success_response
+        
+        openid_req_msg = Message.from_openid_args({
+          'mode' => 'id_res',
+          'ns' => OPENID2_NS,
+          'ns.pape' => PAPE::NS_URI,
+          'pape.auth_policies' => [PAPE::AUTH_MULTI_FACTOR, PAPE::AUTH_PHISHING_RESISTANT].join(' '),
+          'pape.auth_time' => '1983-11-05T12:30:24Z'
+          })
+        signed_stuff = {
+          'auth_policies' => [PAPE::AUTH_MULTI_FACTOR, PAPE::AUTH_PHISHING_RESISTANT].join(' '),
+          'auth_time' => '1983-11-05T12:30:24Z'
+        }
+        oid_req = DummySuccessResponse.new(openid_req_msg, signed_stuff)
+        req = PAPE::Response.from_success_response(oid_req)
+        assert_equal([PAPE::AUTH_MULTI_FACTOR, PAPE::AUTH_PHISHING_RESISTANT], req.auth_policies)
+        assert_equal('1983-11-05T12:30:24Z', req.auth_time)
+      end
+
+      def test_from_success_response_unsigned
+        openid_req_msg = Message.from_openid_args({
+          'mode' => 'id_res',
+          'ns' => OPENID2_NS,
+          'ns.pape' => PAPE::NS_URI,
+          'pape.auth_policies' => [PAPE::AUTH_MULTI_FACTOR, PAPE::AUTH_PHISHING_RESISTANT].join(' '),
+          'pape.auth_time' => '1983-11-05T12:30:24Z'
+          })
+        signed_stuff = {}
+        endpoint = OpenIDServiceEndpoint.new
+        oid_req = Consumer::SuccessResponse.new(endpoint, openid_req_msg, signed_stuff)
+        req = PAPE::Response.from_success_response(oid_req)
+        assert(req.nil?, req.inspect)
+      end
+    end
+  end
+end
diff --git a/vendor/gems/ruby-openid-2.1.4/test/test_parsehtml.rb b/vendor/gems/ruby-openid-2.1.4/test/test_parsehtml.rb
new file mode 100644 (file)
index 0000000..49542a6
--- /dev/null
@@ -0,0 +1,80 @@
+require 'test/unit'
+require "openid/yadis/parsehtml"
+require "testutil"
+
+module OpenID
+  class ParseHTMLTestCase < Test::Unit::TestCase
+    include OpenID::TestDataMixin
+
+    def test_parsehtml
+      reserved_values = ['None', 'EOF']
+      chunks = read_data_file('test1-parsehtml.txt', false).split("\f\n")
+      test_num = 1
+
+      chunks.each{|c|
+        expected, html = c.split("\n", 2)
+        found = Yadis::html_yadis_location(html)
+
+        assert(!reserved_values.member?(found))
+
+        # this case is a little hard to detect and the distinction
+        # seems unimportant
+        expected = "None" if expected == "EOF"
+
+        found = "None" if found.nil?
+        assert_equal(expected, found, html.split("\n",2)[0])
+      }
+    end
+  end
+
+  # the HTML tokenizer test
+  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
+
diff --git a/vendor/gems/ruby-openid-2.1.4/test/test_responses.rb b/vendor/gems/ruby-openid-2.1.4/test/test_responses.rb
new file mode 100644 (file)
index 0000000..61a0e4c
--- /dev/null
@@ -0,0 +1,63 @@
+require "test/unit"
+require "openid/consumer/discovery"
+require "openid/consumer/responses"
+
+module OpenID
+  class Consumer
+    module TestResponses
+      class TestSuccessResponse < Test::Unit::TestCase
+        def setup
+          @endpoint = OpenIDServiceEndpoint.new
+          @endpoint.claimed_id = 'identity_url'
+        end
+
+        def test_extension_response
+          q = {
+            'ns.sreg' => 'urn:sreg',
+            'ns.unittest' => 'urn:unittest',
+            'unittest.one' => '1',
+            'unittest.two' => '2',
+            'sreg.nickname' => 'j3h',
+            'return_to' => 'return_to',
+          }
+          signed_list = q.keys.map { |k| 'openid.' + k }
+          msg = Message.from_openid_args(q)
+          resp = SuccessResponse.new(@endpoint, msg, signed_list)
+          utargs = resp.extension_response('urn:unittest', false)
+          assert_equal(utargs, {'one' => '1', 'two' => '2'})
+          sregargs = resp.extension_response('urn:sreg', false)
+          assert_equal(sregargs, {'nickname' => 'j3h'})
+        end
+
+        def test_extension_response_signed
+          args = {
+            'ns.sreg' => 'urn:sreg',
+            'ns.unittest' => 'urn:unittest',
+            'unittest.one' => '1',
+            'unittest.two' => '2',
+            'sreg.nickname' => 'j3h',
+            'sreg.dob' => 'yesterday',
+            'return_to' => 'return_to',
+            'signed' => 'sreg.nickname,unittest.one,sreg.dob',
+          }
+
+          signed_list = ['openid.sreg.nickname',
+                         'openid.unittest.one',
+                         'openid.sreg.dob',]
+
+          msg = Message.from_openid_args(args)
+          resp = SuccessResponse.new(@endpoint, msg, signed_list)
+
+          # All args in this NS are signed, so expect all.
+          sregargs = resp.extension_response('urn:sreg', true)
+          assert_equal(sregargs, {'nickname' => 'j3h', 'dob' => 'yesterday'})
+
+          # Not all args in this NS are signed, so expect nil when
+          # asking for them.
+          utargs = resp.extension_response('urn:unittest', true)
+          assert_equal(nil, utargs)
+        end
+      end
+    end
+  end
+end
diff --git a/vendor/gems/ruby-openid-2.1.4/test/test_server.rb b/vendor/gems/ruby-openid-2.1.4/test/test_server.rb
new file mode 100644 (file)
index 0000000..11d0f56
--- /dev/null
@@ -0,0 +1,2457 @@
+require 'openid/server'
+require 'openid/cryptutil'
+require 'openid/association'
+require 'openid/util'
+require 'openid/message'
+require 'openid/store/memory'
+require 'openid/dh'
+require 'openid/consumer/associationmanager'
+require 'util'
+require "testutil"
+
+require 'test/unit'
+require 'uri'
+
+# In general, if you edit or add tests here, try to move in the
+# direction of testing smaller units.  For testing the external
+# interfaces, we'll be developing an implementation-agnostic testing
+# suite.
+
+# for more, see /etc/ssh/moduli
+
+module OpenID
+
+  ALT_MODULUS = 0xCAADDDEC1667FC68B5FA15D53C4E1532DD24561A1A2D47A12C01ABEA1E00731F6921AAC40742311FDF9E634BB7131BEE1AF240261554389A910425E044E88C8359B010F5AD2B80E29CB1A5B027B19D9E01A6F63A6F45E5D7ED2FF6A2A0085050A7D0CF307C3DB51D2490355907B4427C23A98DF1EB8ABEF2BA209BB7AFFE86A7
+  ALT_GEN = 5
+
+  class CatchLogs
+    def catchlogs_setup
+      @old_logger = Util.logger
+      Util.logger = self.method('got_log_message')
+      @messages = []
+    end
+
+    def got_log_message(message)
+      @messages << message
+    end
+
+    def teardown
+      Util.logger = @old_logger
+    end
+  end
+
+  class TestProtocolError < Test::Unit::TestCase
+    def test_browserWithReturnTo
+      return_to = "http://rp.unittest/consumer"
+      # will be a ProtocolError raised by Decode or
+      # CheckIDRequest.answer
+      args = Message.from_post_args({
+                                      'openid.mode' => 'monkeydance',
+                                      'openid.identity' => 'http://wagu.unittest/',
+                                      'openid.return_to' => return_to,
+                                    })
+      e = Server::ProtocolError.new(args, "plucky")
+      assert(e.has_return_to)
+      expected_args = {
+        'openid.mode' => 'error',
+        'openid.error' => 'plucky',
+      }
+
+      rt_base, result_args = e.encode_to_url.split('?', 2)
+      result_args = Util.parse_query(result_args)
+      assert_equal(result_args, expected_args)
+    end
+
+    def test_browserWithReturnTo_OpenID2_GET
+      return_to = "http://rp.unittest/consumer"
+      # will be a ProtocolError raised by Decode or
+      # CheckIDRequest.answer
+      args = Message.from_post_args({
+                                      'openid.ns' => OPENID2_NS,
+                                      'openid.mode' => 'monkeydance',
+                                      'openid.identity' => 'http://wagu.unittest/',
+                                      'openid.claimed_id' => 'http://wagu.unittest/',
+                                      'openid.return_to' => return_to,
+                                    })
+      e = Server::ProtocolError.new(args, "plucky")
+      assert(e.has_return_to)
+      expected_args = {
+        'openid.ns' => OPENID2_NS,
+        'openid.mode' => 'error',
+        'openid.error' => 'plucky',
+      }
+
+      rt_base, result_args = e.encode_to_url.split('?', 2)
+      result_args = Util.parse_query(result_args)
+      assert_equal(result_args, expected_args)
+    end
+
+    def test_browserWithReturnTo_OpenID2_POST
+      return_to = "http://rp.unittest/consumer" + ('x' * OPENID1_URL_LIMIT)
+      # will be a ProtocolError raised by Decode or
+      # CheckIDRequest.answer
+      args = Message.from_post_args({
+                                      'openid.ns' => OPENID2_NS,
+                                      'openid.mode' => 'monkeydance',
+                                      'openid.identity' => 'http://wagu.unittest/',
+                                      'openid.claimed_id' => 'http://wagu.unittest/',
+                                      'openid.return_to' => return_to,
+                                    })
+      e = Server::ProtocolError.new(args, "plucky")
+      assert(e.has_return_to)
+      expected_args = {
+        'openid.ns' => OPENID2_NS,
+        'openid.mode' => 'error',
+        'openid.error' => 'plucky',
+      }
+
+      assert(e.which_encoding == Server::ENCODE_HTML_FORM)
+      assert(e.to_form_markup == e.to_message.to_form_markup(
+                                                             args.get_arg(OPENID_NS, 'return_to')))
+    end
+
+    def test_browserWithReturnTo_OpenID1_exceeds_limit
+      return_to = "http://rp.unittest/consumer" + ('x' * OPENID1_URL_LIMIT)
+      # will be a ProtocolError raised by Decode or
+      # CheckIDRequest.answer
+      args = Message.from_post_args({
+                                      'openid.mode' => 'monkeydance',
+                                      'openid.identity' => 'http://wagu.unittest/',
+                                      'openid.return_to' => return_to,
+                                    })
+      e = Server::ProtocolError.new(args, "plucky")
+      assert(e.has_return_to)
+      expected_args = {
+        'openid.mode' => 'error',
+        'openid.error' => 'plucky',
+      }
+
+      assert(e.which_encoding == Server::ENCODE_URL)
+
+      rt_base, result_args = e.encode_to_url.split('?', 2)
+      result_args = Util.parse_query(result_args)
+      assert_equal(result_args, expected_args)
+    end
+
+    def test_noReturnTo
+      # will be a ProtocolError raised by Decode or
+      # CheckIDRequest.answer
+      args = Message.from_post_args({
+                                      'openid.mode' => 'zebradance',
+                                      'openid.identity' => 'http://wagu.unittest/',
+                                    })
+      e = Server::ProtocolError.new(args, "waffles")
+      assert(!e.has_return_to)
+      expected = "error:waffles\nmode:error\n"
+      assert_equal(e.encode_to_kvform, expected)
+    end
+
+    def test_no_message
+      e = Server::ProtocolError.new(nil, "no message")
+      assert(e.get_return_to.nil?)
+      assert_equal(e.which_encoding, nil)
+    end
+
+    def test_which_encoding_no_message
+      e = Server::ProtocolError.new(nil, "no message")
+      assert(e.which_encoding.nil?)
+    end
+  end
+
+  class TestDecode < Test::Unit::TestCase
+    def setup
+      @claimed_id = 'http://de.legating.de.coder.unittest/'
+      @id_url = "http://decoder.am.unittest/"
+      @rt_url = "http://rp.unittest/foobot/?qux=zam"
+      @tr_url = "http://rp.unittest/"
+      @assoc_handle = "{assoc}{handle}"
+      @op_endpoint = 'http://endpoint.unittest/encode'
+      @store = Store::Memory.new()
+      @server = Server::Server.new(@store, @op_endpoint)
+      @decode = Server::Decoder.new(@server).method('decode')
+    end
+
+    def test_none
+      args = {}
+      r = @decode.call(args)
+      assert_equal(r, nil)
+    end
+
+    def test_irrelevant
+      args = {
+        'pony' => 'spotted',
+        'sreg.mutant_power' => 'decaffinator',
+      }
+      assert_raise(Server::ProtocolError) {
+        @decode.call(args)
+      }
+    end
+
+    def test_bad
+      args = {
+        'openid.mode' => 'twos-compliment',
+        'openid.pants' => 'zippered',
+      }
+      assert_raise(Server::ProtocolError) {
+        @decode.call(args)
+      }
+    end
+
+    def test_dictOfLists
+      args = {
+        'openid.mode' => ['checkid_setup'],
+        'openid.identity' => @id_url,
+        'openid.assoc_handle' => @assoc_handle,
+        'openid.return_to' => @rt_url,
+        'openid.trust_root' => @tr_url,
+      }
+      begin
+        result = @decode.call(args)
+      rescue ArgumentError => err
+        assert(!err.to_s.index('values').nil?, err)
+      else
+        flunk("Expected ArgumentError, but got result #{result}")
+      end
+    end
+
+    def test_checkidImmediate
+      args = {
+        'openid.mode' => 'checkid_immediate',
+        'openid.identity' => @id_url,
+        'openid.assoc_handle' => @assoc_handle,
+        'openid.return_to' => @rt_url,
+        'openid.trust_root' => @tr_url,
+        # should be ignored
+        'openid.some.extension' => 'junk',
+      }
+      r = @decode.call(args)
+      assert(r.is_a?(Server::CheckIDRequest))
+      assert_equal(r.mode, "checkid_immediate")
+      assert_equal(r.immediate, true)
+      assert_equal(r.identity, @id_url)
+      assert_equal(r.trust_root, @tr_url)
+      assert_equal(r.return_to, @rt_url)
+      assert_equal(r.assoc_handle, @assoc_handle)
+    end
+
+    def test_checkidImmediate_constructor
+      r = Server::CheckIDRequest.new(@id_url, @rt_url, nil,
+                                     @rt_url, true, @assoc_handle)
+      assert(r.mode == 'checkid_immediate')
+      assert(r.immediate)
+    end
+
+    def test_checkid_missing_return_to_and_trust_root
+      args = {
+        'openid.ns' => OPENID2_NS,
+        'openid.mode' => 'checkid_setup',
+        'openid.identity' => @id_url,
+        'openid.claimed_id' => @id_url,
+        'openid.assoc_handle' => @assoc_handle,
+      }
+      assert_raise(Server::ProtocolError) {
+        m = Message.from_post_args(args)
+        Server::CheckIDRequest.from_message(m, @op_endpoint)
+      }
+    end
+
+    def test_checkid_id_select
+      args = {
+        'openid.ns' => OPENID2_NS,
+        'openid.mode' => 'checkid_setup',
+        'openid.identity' => IDENTIFIER_SELECT,
+        'openid.claimed_id' => IDENTIFIER_SELECT,
+        'openid.assoc_handle' => @assoc_handle,
+        'openid.return_to' => @rt_url,
+        'openid.realm' => @tr_url,
+      }
+      m = Message.from_post_args(args)
+      req = Server::CheckIDRequest.from_message(m, @op_endpoint)
+      assert(req.id_select)
+    end
+
+    def test_checkid_not_id_select
+      args = {
+        'openid.ns' => OPENID2_NS,
+        'openid.mode' => 'checkid_setup',
+        'openid.assoc_handle' => @assoc_handle,
+        'openid.return_to' => @rt_url,
+        'openid.realm' => @tr_url,
+      }
+
+      id_args = [
+                 {'openid.claimed_id' => IDENTIFIER_SELECT,
+                   'openid.identity' => 'http://bogus.com/'},
+
+                 {'openid.claimed_id' => 'http://bogus.com/',
+                   'openid.identity' => 'http://bogus.com/'},
+                ]
+
+      id_args.each { |id|
+        m = Message.from_post_args(args.merge(id))
+        req = Server::CheckIDRequest.from_message(m, @op_endpoint)
+        assert(!req.id_select)
+      }
+    end
+
+    def test_checkidSetup
+      args = {
+        'openid.mode' => 'checkid_setup',
+        'openid.identity' => @id_url,
+        'openid.assoc_handle' => @assoc_handle,
+        'openid.return_to' => @rt_url,
+        'openid.trust_root' => @tr_url,
+      }
+      r = @decode.call(args)
+      assert(r.is_a?(Server::CheckIDRequest))
+      assert_equal(r.mode, "checkid_setup")
+      assert_equal(r.immediate, false)
+      assert_equal(r.identity, @id_url)
+      assert_equal(r.trust_root, @tr_url)
+      assert_equal(r.return_to, @rt_url)
+    end
+
+    def test_checkidSetupOpenID2
+      args = {
+        'openid.ns' => OPENID2_NS,
+        'openid.mode' => 'checkid_setup',
+        'openid.identity' => @id_url,
+        'openid.claimed_id' => @claimed_id,
+        'openid.assoc_handle' => @assoc_handle,
+        'openid.return_to' => @rt_url,
+        'openid.realm' => @tr_url,
+      }
+      r = @decode.call(args)
+      assert(r.is_a?(Server::CheckIDRequest))
+      assert_equal(r.mode, "checkid_setup")
+      assert_equal(r.immediate, false)
+      assert_equal(r.identity, @id_url)
+      assert_equal(r.claimed_id, @claimed_id)
+      assert_equal(r.trust_root, @tr_url)
+      assert_equal(r.return_to, @rt_url)
+    end
+
+    def test_checkidSetupNoClaimedIDOpenID2
+      args = {
+        'openid.ns' => OPENID2_NS,
+        'openid.mode' => 'checkid_setup',
+        'openid.identity' => @id_url,
+        'openid.assoc_handle' => @assoc_handle,
+        'openid.return_to' => @rt_url,
+        'openid.realm' => @tr_url,
+      }
+      assert_raise(Server::ProtocolError) {
+        @decode.call(args)
+      }
+    end
+
+    def test_checkidSetupNoIdentityOpenID2
+      args = {
+        'openid.ns' => OPENID2_NS,
+        'openid.mode' => 'checkid_setup',
+        'openid.assoc_handle' => @assoc_handle,
+        'openid.return_to' => @rt_url,
+        'openid.realm' => @tr_url,
+      }
+      r = @decode.call(args)
+      assert(r.is_a?(Server::CheckIDRequest))
+      assert_equal(r.mode, "checkid_setup")
+      assert_equal(r.immediate, false)
+      assert_equal(r.identity, nil)
+      assert_equal(r.trust_root, @tr_url)
+      assert_equal(r.return_to, @rt_url)
+    end
+
+    def test_checkidSetupNoReturnOpenID1
+      # Make sure an OpenID 1 request cannot be decoded if it lacks a
+      # return_to.
+      args = {
+        'openid.mode' => 'checkid_setup',
+        'openid.identity' => @id_url,
+        'openid.assoc_handle' => @assoc_handle,
+        'openid.trust_root' => @tr_url,
+      }
+      assert_raise(Server::ProtocolError) {
+        @decode.call(args)
+      }
+    end
+
+    def test_checkidSetupNoReturnOpenID2
+      # Make sure an OpenID 2 request with no return_to can be decoded,
+      # and make sure a response to such a request raises
+      # NoReturnToError.
+      args = {
+        'openid.ns' => OPENID2_NS,
+        'openid.mode' => 'checkid_setup',
+        'openid.identity' => @id_url,
+        'openid.claimed_id' => @id_url,
+        'openid.assoc_handle' => @assoc_handle,
+        'openid.realm' => @tr_url,
+      }
+
+      req = @decode.call(args)
+      assert(req.is_a?(Server::CheckIDRequest))
+
+      assert_raise(Server::NoReturnToError) {
+        req.answer(false)
+      }
+
+      assert_raise(Server::NoReturnToError) {
+        req.encode_to_url('bogus')
+      }
+
+      assert_raise(Server::NoReturnToError) {
+        req.cancel_url
+      }
+    end
+
+    def test_checkidSetupRealmRequiredOpenID2
+      # Make sure that an OpenID 2 request which lacks return_to cannot
+      # be decoded if it lacks a realm.  Spec => This value
+      # (openid.realm) MUST be sent if openid.return_to is omitted.
+      args = {
+        'openid.ns' => OPENID2_NS,
+        'openid.mode' => 'checkid_setup',
+        'openid.identity' => @id_url,
+        'openid.assoc_handle' => @assoc_handle,
+      }
+      assert_raise(Server::ProtocolError) {
+        @decode.call(args)
+      }
+    end
+
+    def test_checkidSetupBadReturn
+      args = {
+        'openid.mode' => 'checkid_setup',
+        'openid.identity' => @id_url,
+        'openid.assoc_handle' => @assoc_handle,
+        'openid.return_to' => 'not a url',
+      }
+      begin
+        result = @decode.call(args)
+      rescue Server::ProtocolError => err
+        assert(err.openid_message)
+      else
+        flunk("Expected ProtocolError, instead returned with #{result}")
+      end
+    end
+
+    def test_checkidSetupUntrustedReturn
+      args = {
+        'openid.mode' => 'checkid_setup',
+        'openid.identity' => @id_url,
+        'openid.assoc_handle' => @assoc_handle,
+        'openid.return_to' => @rt_url,
+        'openid.trust_root' => 'http://not-the-return-place.unittest/',
+      }
+      begin
+        result = @decode.call(args)
+      rescue Server::UntrustedReturnURL => err
+        assert(err.openid_message, err.to_s)
+      else
+        flunk("Expected UntrustedReturnURL, instead returned with #{result}")
+      end
+    end
+
+    def test_checkidSetupUntrustedReturn_Constructor
+      assert_raise(Server::UntrustedReturnURL) {
+        Server::CheckIDRequest.new(@id_url, @rt_url, nil,
+                                   'http://not-the-return-place.unittest/',
+                                   false, @assoc_handle)
+      }
+    end
+
+    def test_checkidSetupMalformedReturnURL_Constructor
+      assert_raise(Server::MalformedReturnURL) {
+        Server::CheckIDRequest.new(@id_url, 'bogus://return.url', nil,
+                                   'http://trustroot.com/',
+                                   false, @assoc_handle)
+      }
+    end
+
+    def test_checkAuth
+      args = {
+        'openid.mode' => 'check_authentication',
+        'openid.assoc_handle' => '{dumb}{handle}',
+        'openid.sig' => 'sigblob',
+        'openid.signed' => 'identity,return_to,response_nonce,mode',
+        'openid.identity' => 'signedval1',
+        'openid.return_to' => 'signedval2',
+        'openid.response_nonce' => 'signedval3',
+        'openid.baz' => 'unsigned',
+      }
+      r = @decode.call(args)
+      assert(r.is_a?(Server::CheckAuthRequest))
+      assert_equal(r.mode, 'check_authentication')
+      assert_equal(r.sig, 'sigblob')
+    end
+
+    def test_checkAuthMissingSignature
+      args = {
+        'openid.mode' => 'check_authentication',
+        'openid.assoc_handle' => '{dumb}{handle}',
+        'openid.signed' => 'foo,bar,mode',
+        'openid.foo' => 'signedval1',
+        'openid.bar' => 'signedval2',
+        'openid.baz' => 'unsigned',
+      }
+      assert_raise(Server::ProtocolError) {
+        @decode.call(args)
+      }
+    end
+
+    def test_checkAuthAndInvalidate
+      args = {
+        'openid.mode' => 'check_authentication',
+        'openid.assoc_handle' => '{dumb}{handle}',
+        'openid.invalidate_handle' => '[[SMART_handle]]',
+        'openid.sig' => 'sigblob',
+        'openid.signed' => 'identity,return_to,response_nonce,mode',
+        'openid.identity' => 'signedval1',
+        'openid.return_to' => 'signedval2',
+        'openid.response_nonce' => 'signedval3',
+        'openid.baz' => 'unsigned',
+      }
+      r = @decode.call(args)
+      assert(r.is_a?(Server::CheckAuthRequest))
+      assert_equal(r.invalidate_handle, '[[SMART_handle]]')
+    end
+
+    def test_associateDH
+      args = {
+        'openid.mode' => 'associate',
+        'openid.session_type' => 'DH-SHA1',
+        'openid.dh_consumer_public' => "Rzup9265tw==",
+      }
+      r = @decode.call(args)
+      assert(r.is_a?(Server::AssociateRequest))
+      assert_equal(r.mode, "associate")
+      assert_equal(r.session.session_type, "DH-SHA1")
+      assert_equal(r.assoc_type, "HMAC-SHA1")
+      assert(r.session.consumer_pubkey)
+    end
+
+    def test_associateDHMissingKey
+      # Trying DH assoc w/o public key
+      args = {
+        'openid.mode' => 'associate',
+        'openid.session_type' => 'DH-SHA1',
+      }
+      # Using DH-SHA1 without supplying dh_consumer_public is an error.
+      assert_raise(Server::ProtocolError) {
+        @decode.call(args)
+      }
+    end
+
+    def test_associateDHpubKeyNotB64
+      args = {
+        'openid.mode' => 'associate',
+        'openid.session_type' => 'DH-SHA1',
+        'openid.dh_consumer_public' => "donkeydonkeydonkey",
+      }
+      assert_raise(Server::ProtocolError) {
+        @decode.call(args)
+      }
+    end
+
+    def test_associateDHModGen
+      # test dh with non-default but valid values for dh_modulus and
+      # dh_gen
+      args = {
+        'openid.mode' => 'associate',
+        'openid.session_type' => 'DH-SHA1',
+        'openid.dh_consumer_public' => "Rzup9265tw==",
+        'openid.dh_modulus' => CryptUtil.num_to_base64(ALT_MODULUS),
+        'openid.dh_gen' => CryptUtil.num_to_base64(ALT_GEN) ,
+      }
+      r = @decode.call(args)
+      assert(r.is_a?(Server::AssociateRequest))
+      assert_equal(r.mode, "associate")
+      assert_equal(r.session.session_type, "DH-SHA1")
+      assert_equal(r.assoc_type, "HMAC-SHA1")
+      assert_equal(r.session.dh.modulus, ALT_MODULUS)
+      assert_equal(r.session.dh.generator, ALT_GEN)
+      assert(r.session.consumer_pubkey)
+    end
+
+    def test_associateDHCorruptModGen
+      # test dh with non-default but valid values for dh_modulus and
+      # dh_gen
+      args = {
+        'openid.mode' => 'associate',
+        'openid.session_type' => 'DH-SHA1',
+        'openid.dh_consumer_public' => "Rzup9265tw==",
+        'openid.dh_modulus' => 'pizza',
+        'openid.dh_gen' => 'gnocchi',
+      }
+      assert_raise(Server::ProtocolError) {
+        @decode.call(args)
+      }
+    end
+
+    def test_associateDHMissingGen
+      args = {
+        'openid.mode' => 'associate',
+        'openid.session_type' => 'DH-SHA1',
+        'openid.dh_consumer_public' => "Rzup9265tw==",
+        'openid.dh_modulus' => 'pizza',
+      }
+      assert_raise(Server::ProtocolError) {
+        @decode.call(args)
+      }
+    end
+
+    def test_associateDHMissingMod
+      args = {
+        'openid.mode' => 'associate',
+        'openid.session_type' => 'DH-SHA1',
+        'openid.dh_consumer_public' => "Rzup9265tw==",
+        'openid.dh_gen' => 'pizza',
+      }
+      assert_raise(Server::ProtocolError) {
+        @decode.call(args)
+      }
+    end
+
+    #     def test_associateDHInvalidModGen(self):
+    #         # test dh with properly encoded values that are not a valid
+    #         #   modulus/generator combination.
+    #         args = {
+    #             'openid.mode': 'associate',
+    #             'openid.session_type': 'DH-SHA1',
+    #             'openid.dh_consumer_public': "Rzup9265tw==",
+    #             'openid.dh_modulus': cryptutil.longToBase64(9),
+    #             'openid.dh_gen': cryptutil.longToBase64(27) ,
+    #             }
+    #         self.failUnlessRaises(server.ProtocolError, self.decode, args)
+    #     test_associateDHInvalidModGen.todo = "low-priority feature"
+
+    def test_associateWeirdSession
+      args = {
+        'openid.mode' => 'associate',
+        'openid.session_type' => 'FLCL6',
+        'openid.dh_consumer_public' => "YQ==\n",
+      }
+      assert_raise(Server::ProtocolError) {
+        @decode.call(args)
+      }
+    end
+
+    def test_associatePlain
+      args = {
+        'openid.mode' => 'associate',
+      }
+      r = @decode.call(args)
+      assert(r.is_a?(Server::AssociateRequest))
+      assert_equal(r.mode, "associate")
+      assert_equal(r.session.session_type, "no-encryption")
+      assert_equal(r.assoc_type, "HMAC-SHA1")
+    end
+
+    def test_nomode
+      args = {
+        'openid.session_type' => 'DH-SHA1',
+        'openid.dh_consumer_public' => "my public keeey",
+      }
+      assert_raise(Server::ProtocolError) {
+        @decode.call(args)
+      }
+    end
+
+    def test_invalidns
+      args = {'openid.ns' => 'Vegetables',
+              'openid.mode' => 'associate'}
+      begin
+        r = @decode.call(args)
+      rescue Server::ProtocolError => err
+        assert(err.openid_message)
+        assert(err.to_s.index('Vegetables'))
+      end
+    end
+  end
+
+  class BogusEncoder < Server::Encoder
+    def encode(response)
+      return "BOGUS"
+    end
+  end
+
+  class BogusDecoder < Server::Decoder
+    def decode(query)
+      return "BOGUS"
+    end
+  end
+
+  class TestEncode < Test::Unit::TestCase
+    def setup
+      @encoder = Server::Encoder.new
+      @encode = @encoder.method('encode')
+      @op_endpoint = 'http://endpoint.unittest/encode'
+      @store = Store::Memory.new
+      @server = Server::Server.new(@store, @op_endpoint)
+    end
+
+    def test_id_res_OpenID2_GET
+      # Check that when an OpenID 2 response does not exceed the OpenID
+      # 1 message size, a GET response (i.e., redirect) is issued.
+      request = Server::CheckIDRequest.new(
+                                   'http://bombom.unittest/',
+                                   'http://burr.unittest/999',
+                                   @server.op_endpoint,
+                                   'http://burr.unittest/',
+                                   false,
+                                   nil)
+      request.message = Message.new(OPENID2_NS)
+      response = Server::OpenIDResponse.new(request)
+      response.fields = Message.from_openid_args({
+                                                   'ns' => OPENID2_NS,
+                                                   'mode' => 'id_res',
+                                                   'identity' => request.identity,
+                                                   'claimed_id' => request.identity,
+                                                   'return_to' => request.return_to,
+                                                 })
+
+      assert(!response.render_as_form)
+      assert(response.which_encoding == Server::ENCODE_URL)
+      webresponse = @encode.call(response)
+      assert(webresponse.headers.member?('location'))
+    end
+
+    def test_id_res_OpenID2_POST
+      # Check that when an OpenID 2 response exceeds the OpenID 1
+      # message size, a POST response (i.e., an HTML form) is returned.
+      request = Server::CheckIDRequest.new(
+                                   'http://bombom.unittest/',
+                                   'http://burr.unittest/999',
+                                   @server.op_endpoint,
+                                   'http://burr.unittest/',
+                                   false,
+                                   nil)
+      request.message = Message.new(OPENID2_NS)
+      response = Server::OpenIDResponse.new(request)
+      response.fields = Message.from_openid_args({
+                                                   'ns' => OPENID2_NS,
+                                                   'mode' => 'id_res',
+                                                   'identity' => request.identity,
+                                                   'claimed_id' => request.identity,
+                                                   'return_to' => 'x' * OPENID1_URL_LIMIT,
+                                                 })
+
+      assert(response.render_as_form)
+      assert(response.encode_to_url.length > OPENID1_URL_LIMIT)
+      assert(response.which_encoding == Server::ENCODE_HTML_FORM)
+      webresponse = @encode.call(response)
+      assert_equal(webresponse.body, response.to_form_markup)
+    end
+
+    def test_to_form_markup
+       request = Server::CheckIDRequest.new(
+                                   'http://bombom.unittest/',
+                                   'http://burr.unittest/999',
+                                   @server.op_endpoint,
+                                   'http://burr.unittest/',
+                                   false,
+                                   nil)
+      request.message = Message.new(OPENID2_NS)
+      response = Server::OpenIDResponse.new(request)
+      response.fields = Message.from_openid_args({
+                                                   'ns' => OPENID2_NS,
+                                                   'mode' => 'id_res',
+                                                   'identity' => request.identity,
+                                                   'claimed_id' => request.identity,
+                                                   'return_to' => 'x' * OPENID1_URL_LIMIT,
+                                                 })
+      form_markup = response.to_form_markup({'foo'=>'bar'})
+      assert(/ foo="bar"/ =~ form_markup, form_markup)
+    end
+
+    def test_to_html
+       request = Server::CheckIDRequest.new(
+                                   'http://bombom.unittest/',
+                                   'http://burr.unittest/999',
+                                   @server.op_endpoint,
+                                   'http://burr.unittest/',
+                                   false,
+                                   nil)
+      request.message = Message.new(OPENID2_NS)
+      response = Server::OpenIDResponse.new(request)
+      response.fields = Message.from_openid_args({
+                                                   'ns' => OPENID2_NS,
+                                                   'mode' => 'id_res',
+                                                   'identity' => request.identity,
+                                                   'claimed_id' => request.identity,
+                                                   'return_to' => 'x' * OPENID1_URL_LIMIT,
+                                                 })
+      html = response.to_html
+      assert(html)
+    end
+
+    def test_id_res_OpenID1_exceeds_limit
+      # Check that when an OpenID 1 response exceeds the OpenID 1
+      # message size, a GET response is issued.  Technically, this
+      # shouldn't be permitted by the library, but this test is in place
+      # to preserve the status quo for OpenID 1.
+      request = Server::CheckIDRequest.new(
+                                   'http://bombom.unittest/',
+                                   'http://burr.unittest/999',
+                                   @server.op_endpoint,
+                                   'http://burr.unittest/',
+                                   false,
+                                   nil)
+      request.message = Message.new(OPENID1_NS)
+
+      response = Server::OpenIDResponse.new(request)
+      response.fields = Message.from_openid_args({
+                                                   'mode' => 'id_res',
+                                                   'identity' => request.identity,
+                                                   'return_to' => 'x' * OPENID1_URL_LIMIT,
+                                                 })
+
+      assert(!response.render_as_form)
+      assert(response.encode_to_url.length > OPENID1_URL_LIMIT)
+      assert(response.which_encoding == Server::ENCODE_URL)
+      webresponse = @encode.call(response)
+      assert_equal(webresponse.headers['location'], response.encode_to_url)
+    end
+
+    def test_id_res
+      request = Server::CheckIDRequest.new(
+                                   'http://bombom.unittest/',
+                                   'http://burr.unittest/999',
+                                   @server.op_endpoint,
+                                   'http://burr.unittest/',
+                                   false, nil)
+      request.message = Message.new(OPENID1_NS)
+      response = Server::OpenIDResponse.new(request)
+      response.fields = Message.from_openid_args({
+                                                   'mode' => 'id_res',
+                                                   'identity' => request.identity,
+                                                   'return_to' => request.return_to,
+                                                 })
+      webresponse = @encode.call(response)
+      assert_equal(webresponse.code, Server::HTTP_REDIRECT)
+      assert(webresponse.headers.member?('location'))
+
+      location = webresponse.headers['location']
+      assert(location.starts_with?(request.return_to),
+             sprintf("%s does not start with %s",
+                     location, request.return_to))
+      # argh.
+      q2 = Util.parse_query(URI::parse(location).query)
+      expected = response.fields.to_post_args
+      assert_equal(q2, expected)
+    end
+
+    def test_cancel
+      request = Server::CheckIDRequest.new(
+                                   'http://bombom.unittest/',
+                                   'http://burr.unittest/999',
+                                   @server.op_endpoint,
+                                   'http://burr.unittest/',
+                                   false, nil)
+      request.message = Message.new(OPENID2_NS)
+      response = Server::OpenIDResponse.new(request)
+      response.fields = Message.from_openid_args({
+                                                   'mode' => 'cancel',
+                                                 })
+      webresponse = @encode.call(response)
+      assert_equal(webresponse.code, Server::HTTP_REDIRECT)
+      assert(webresponse.headers.member?('location'))
+    end
+
+    def test_cancel_to_form
+      request = Server::CheckIDRequest.new(
+                                   'http://bombom.unittest/',
+                                   'http://burr.unittest/999',
+                                   @server.op_endpoint,
+                                   'http://burr.unittest/',
+                                   false, nil)
+      request.message = Message.new(OPENID2_NS)
+      response = Server::OpenIDResponse.new(request)
+      response.fields = Message.from_openid_args({
+                                                   'mode' => 'cancel',
+                                                 })
+      form = response.to_form_markup
+      assert(form.index(request.return_to))
+    end
+
+    def test_assocReply
+      msg = Message.new(OPENID2_NS)
+      msg.set_arg(OPENID2_NS, 'session_type', 'no-encryption')
+      request = Server::AssociateRequest.from_message(msg)
+      response = Server::OpenIDResponse.new(request)
+      response.fields = Message.from_post_args(
+                                               {'openid.assoc_handle' => "every-zig"})
+      webresponse = @encode.call(response)
+      body = "assoc_handle:every-zig\n"
+
+      assert_equal(webresponse.code, Server::HTTP_OK)
+      assert_equal(webresponse.headers, {})
+      assert_equal(webresponse.body, body)
+    end
+
+    def test_checkauthReply
+      request = Server::CheckAuthRequest.new('a_sock_monkey',
+                                     'siggggg',
+                                     [])
+      request.message = Message.new(OPENID2_NS)
+      response = Server::OpenIDResponse.new(request)
+      response.fields = Message.from_openid_args({
+                                                   'is_valid' => 'true',
+                                                   'invalidate_handle' => 'xXxX:xXXx'
+                                                 })
+      body = "invalidate_handle:xXxX:xXXx\nis_valid:true\n"
+
+      webresponse = @encode.call(response)
+      assert_equal(webresponse.code, Server::HTTP_OK)
+      assert_equal(webresponse.headers, {})
+      assert_equal(webresponse.body, body)
+    end
+
+    def test_unencodableError
+      args = Message.from_post_args({
+                                      'openid.identity' => 'http://limu.unittest/',
+                                    })
+      e = Server::ProtocolError.new(args, "wet paint")
+      assert_raise(Server::EncodingError) {
+        @encode.call(e)
+      }
+    end
+
+    def test_encodableError
+      args = Message.from_post_args({
+                                      'openid.mode' => 'associate',
+                                      'openid.identity' => 'http://limu.unittest/',
+                                    })
+      body="error:snoot\nmode:error\n"
+      webresponse = @encode.call(Server::ProtocolError.new(args, "snoot"))
+      assert_equal(webresponse.code, Server::HTTP_ERROR)
+      assert_equal(webresponse.headers, {})
+      assert_equal(webresponse.body, body)
+    end
+  end
+
+  class TestSigningEncode < Test::Unit::TestCase
+    def setup
+      @_dumb_key = Server::Signatory._dumb_key
+      @_normal_key = Server::Signatory._normal_key
+      @store = Store::Memory.new()
+      @server = Server::Server.new(@store, "http://signing.unittest/enc")
+      @request = Server::CheckIDRequest.new(
+                                    'http://bombom.unittest/',
+                                    'http://burr.unittest/999',
+                                    @server.op_endpoint,
+                                    'http://burr.unittest/',
+                                    false, nil)
+      @request.message = Message.new(OPENID2_NS)
+
+      @response = Server::OpenIDResponse.new(@request)
+      @response.fields = Message.from_openid_args({
+                                                    'mode' => 'id_res',
+                                                    'identity' => @request.identity,
+                                                    'return_to' => @request.return_to,
+                                                  })
+      @signatory = Server::Signatory.new(@store)
+      @encoder = Server::SigningEncoder.new(@signatory)
+      @encode = @encoder.method('encode')
+    end
+
+    def test_idres
+      assoc_handle = '{bicycle}{shed}'
+      @store.store_association(
+                               @_normal_key,
+                               Association.from_expires_in(60, assoc_handle,
+                                                           'sekrit', 'HMAC-SHA1'))
+      @request.assoc_handle = assoc_handle
+      webresponse = @encode.call(@response)
+      assert_equal(webresponse.code, Server::HTTP_REDIRECT)
+      assert(webresponse.headers.member?('location'))
+
+      location = webresponse.headers['location']
+      query = Util.parse_query(URI::parse(location).query)
+      assert(query.member?('openid.sig'))
+      assert(query.member?('openid.assoc_handle'))
+      assert(query.member?('openid.signed'))
+    end
+
+    def test_idresDumb
+      webresponse = @encode.call(@response)
+      assert_equal(webresponse.code, Server::HTTP_REDIRECT)
+      assert(webresponse.headers.has_key?('location'))
+
+      location = webresponse.headers['location']
+      query = Util.parse_query(URI::parse(location).query)
+      assert(query.member?('openid.sig'))
+      assert(query.member?('openid.assoc_handle'))
+      assert(query.member?('openid.signed'))
+    end
+
+    def test_forgotStore
+      @encoder.signatory = nil
+      assert_raise(ArgumentError) {
+        @encode.call(@response)
+      }
+    end
+
+    def test_cancel
+      request = Server::CheckIDRequest.new(
+                                   'http://bombom.unittest/',
+                                   'http://burr.unittest/999',
+                                   @server.op_endpoint,
+                                   'http://burr.unittest/',
+                                   false, nil)
+      request.message = Message.new(OPENID2_NS)
+      response = Server::OpenIDResponse.new(request)
+      response.fields.set_arg(OPENID_NS, 'mode', 'cancel')
+      webresponse = @encode.call(response)
+      assert_equal(webresponse.code, Server::HTTP_REDIRECT)
+      assert(webresponse.headers.has_key?('location'))
+      location = webresponse.headers['location']
+      query = Util.parse_query(URI::parse(location).query)
+      assert(!query.has_key?('openid.sig'), response.fields.to_post_args())
+    end
+
+    def test_assocReply
+      msg = Message.new(OPENID2_NS)
+      msg.set_arg(OPENID2_NS, 'session_type', 'no-encryption')
+      request = Server::AssociateRequest.from_message(msg)
+      response = Server::OpenIDResponse.new(request)
+      response.fields = Message.from_openid_args({'assoc_handle' => "every-zig"})
+      webresponse = @encode.call(response)
+      body = "assoc_handle:every-zig\n"
+      assert_equal(webresponse.code, Server::HTTP_OK)
+      assert_equal(webresponse.headers, {})
+      assert_equal(webresponse.body, body)
+    end
+
+    def test_alreadySigned
+      @response.fields.set_arg(OPENID_NS, 'sig', 'priorSig==')
+      assert_raise(Server::AlreadySigned) {
+        @encode.call(@response)
+      }
+    end
+  end
+
+  class TestCheckID < Test::Unit::TestCase
+    def setup
+      @op_endpoint = 'http://endpoint.unittest/'
+      @store = Store::Memory.new()
+      @server = Server::Server.new(@store, @op_endpoint)
+      @request = Server::CheckIDRequest.new(
+                                    'http://bambam.unittest/',
+                                    'http://bar.unittest/999',
+                                    @server.op_endpoint,
+                                    'http://bar.unittest/',
+                                    false)
+      @request.message = Message.new(OPENID2_NS)
+    end
+
+    def test_trustRootInvalid
+      @request.trust_root = "http://foo.unittest/17"
+      @request.return_to = "http://foo.unittest/39"
+      assert(!@request.trust_root_valid())
+    end
+
+    def test_trustRootInvalid_modified
+      @request.trust_root = "does://not.parse/"
+      @request.message = :sentinel
+      begin
+        result = @request.trust_root_valid
+      rescue Server::MalformedTrustRoot => why
+        assert_equal(:sentinel, why.openid_message)
+      else
+        flunk("Expected MalformedTrustRoot, got #{result.inspect}")
+      end
+    end
+
+    def test_trustRootvalid_absent_trust_root
+      @request.trust_root = nil
+      assert(@request.trust_root_valid())
+    end
+
+    def test_trustRootValid
+      @request.trust_root = "http://foo.unittest/"
+      @request.return_to = "http://foo.unittest/39"
+      assert(@request.trust_root_valid())
+    end
+
+    def test_trustRootValidNoReturnTo
+      request = Server::CheckIDRequest.new(
+                                   'http://bambam.unittest/',
+                                   nil,
+                                   @server.op_endpoint,
+                                   'http://bar.unittest/',
+                                   false)
+
+      assert(request.trust_root_valid())
+    end
+
+    def test_returnToVerified_callsVerify
+      # Make sure that verifyReturnTo is calling the trustroot
+      # function verifyReturnTo
+      # Ensure that exceptions are passed through
+      sentinel = Exception.new()
+
+      __req = @request
+      tc = self
+
+      vrfyExc = Proc.new { |trust_root, return_to|
+        tc.assert_equal(__req.trust_root, trust_root)
+        tc.assert_equal(__req.return_to, return_to)
+        raise sentinel
+      }
+
+      TrustRoot.extend(OverrideMethodMixin)
+
+      TrustRoot.with_method_overridden(:verify_return_to, vrfyExc) do
+        begin
+          @request.return_to_verified()
+          flunk("Expected sentinel to be raised, got success")
+        rescue Exception => e
+          assert(e.equal?(sentinel), [e, sentinel].inspect)
+        end
+      end
+
+      # Ensure that True and False are passed through unchanged
+      constVerify = Proc.new { |val|
+        verify = Proc.new { |trust_root, return_to|
+          tc.assert_equal(__req.trust_root, trust_root)
+          tc.assert_equal(__req.request.return_to, return_to)
+          return val
+        }
+
+        return verify
+      }
+
+      [true, false].each { |val|
+        verifier = constVerify.call(val)
+
+        TrustRoot.with_method_overridden(:verify_return_to, verifier) do
+          assert_equal(val, @request.return_to_verified())
+        end
+      }
+    end
+
+    def _expectAnswer(answer, identity=nil, claimed_id=nil)
+      expected_list = [
+                       ['mode', 'id_res'],
+                       ['return_to', @request.return_to],
+                       ['op_endpoint', @op_endpoint],
+                      ]
+      if identity
+        expected_list << ['identity', identity]
+        if claimed_id
+          expected_list << ['claimed_id', claimed_id]
+        else
+          expected_list << ['claimed_id', identity]
+        end
+      end
+
+      expected_list.each { |k, expected|
+        actual = answer.fields.get_arg(OPENID_NS, k)
+        assert_equal(expected, actual,
+                     sprintf("%s: expected %s, got %s",
+                             k, expected, actual))
+      }
+
+      assert(answer.fields.has_key?(OPENID_NS, 'response_nonce'))
+      assert(answer.fields.get_openid_namespace() == OPENID2_NS)
+
+      # One for nonce, one for ns
+      assert_equal(answer.fields.to_post_args.length,
+                   expected_list.length + 2,
+                   answer.fields.to_post_args.inspect)
+    end
+
+    def test_answerAllow
+      # Check the fields specified by "Positive Assertions"
+      #
+      # including mode=id_res, identity, claimed_id, op_endpoint,
+      # return_to
+      answer = @request.answer(true)
+      assert_equal(answer.request, @request)
+      _expectAnswer(answer, @request.identity)
+    end
+
+    def test_answerAllowDelegatedIdentity
+      @request.claimed_id = 'http://delegating.unittest/'
+      answer = @request.answer(true)
+      _expectAnswer(answer, @request.identity,
+                    @request.claimed_id)
+    end
+
+    def test_answerAllowWithoutIdentityReally
+      @request.identity = nil
+      answer = @request.answer(true)
+      assert_equal(answer.request, @request)
+      _expectAnswer(answer)
+    end
+
+    def test_answerAllowAnonymousFail
+      @request.identity = nil
+      # XXX - Check on this, I think this behavior is legal in OpenID
+      # 2.0?
+      assert_raise(ArgumentError) {
+        @request.answer(true, nil, "=V")
+      }
+    end
+
+    def test_answerAllowWithIdentity
+      @request.identity = IDENTIFIER_SELECT
+      selected_id = 'http://anon.unittest/9861'
+      answer = @request.answer(true, nil, selected_id)
+      _expectAnswer(answer, selected_id)
+    end
+
+    def test_answerAllowWithNoIdentity
+      @request.identity = IDENTIFIER_SELECT
+      selected_id = 'http://anon.unittest/9861'
+      assert_raise(ArgumentError) {
+        answer = @request.answer(true, nil, nil)
+      }
+    end
+
+    def test_immediate_openid1_no_identity
+      @request.message = Message.new(OPENID1_NS)
+      @request.immediate = true
+      @request.mode = 'checkid_immediate'
+      resp = @request.answer(false)
+      assert(resp.fields.get_arg(OPENID_NS, 'mode') == 'id_res')
+    end
+
+    def test_checkid_setup_openid1_no_identity
+      @request.message = Message.new(OPENID1_NS)
+      @request.immediate = false
+      @request.mode = 'checkid_setup'
+      resp = @request.answer(false)
+      assert(resp.fields.get_arg(OPENID_NS, 'mode') == 'cancel')
+    end
+
+    def test_immediate_openid1_no_server_url
+      @request.message = Message.new(OPENID1_NS)
+      @request.immediate = true
+      @request.mode = 'checkid_immediate'
+      @request.op_endpoint = nil
+
+      assert_raise(ArgumentError) {
+        resp = @request.answer(false)
+      }
+    end
+
+    def test_immediate_encode_to_url
+      @request.message = Message.new(OPENID1_NS)
+      @request.immediate = true
+      @request.mode = 'checkid_immediate'
+      @request.trust_root = "BOGUS"
+      @request.assoc_handle = "ASSOC"
+
+      server_url = "http://server.com/server"
+
+      url = @request.encode_to_url(server_url)
+      assert(url.starts_with?(server_url))
+
+      unused, query = url.split("?", 2)
+      args = Util.parse_query(query)
+
+      m = Message.from_post_args(args)
+      assert(m.get_arg(OPENID_NS, 'trust_root') == "BOGUS")
+      assert(m.get_arg(OPENID_NS, 'assoc_handle') == "ASSOC")
+      assert(m.get_arg(OPENID_NS, 'mode'), "checkid_immediate")
+      assert(m.get_arg(OPENID_NS, 'identity') == @request.identity)
+      assert(m.get_arg(OPENID_NS, 'claimed_id') == @request.claimed_id)
+      assert(m.get_arg(OPENID_NS, 'return_to') == @request.return_to)
+    end
+
+    def test_answerAllowWithDelegatedIdentityOpenID2
+      # Answer an IDENTIFIER_SELECT case with a delegated identifier.
+
+      # claimed_id delegates to selected_id here.
+      @request.identity = IDENTIFIER_SELECT
+      selected_id = 'http://anon.unittest/9861'
+      claimed_id = 'http://monkeyhat.unittest/'
+      answer = @request.answer(true, nil, selected_id, claimed_id)
+      _expectAnswer(answer, selected_id, claimed_id)
+    end
+
+    def test_answerAllowWithDelegatedIdentityOpenID1
+      # claimed_id parameter doesn't exist in OpenID 1.
+      @request.message = Message.new(OPENID1_NS)
+      # claimed_id delegates to selected_id here.
+      @request.identity = IDENTIFIER_SELECT
+      selected_id = 'http://anon.unittest/9861'
+      claimed_id = 'http://monkeyhat.unittest/'
+      assert_raise(Server::VersionError) {
+        @request.answer(true, nil, selected_id, claimed_id)
+      }
+    end
+
+    def test_answerAllowWithAnotherIdentity
+      # XXX - Check on this, I think this behavior is legal in OpenID
+      # 2.0?
+      assert_raise(ArgumentError){
+        @request.answer(true, nil, "http://pebbles.unittest/")
+      }
+    end
+
+    def test_answerAllowNoIdentityOpenID1
+      @request.message = Message.new(OPENID1_NS)
+      @request.identity = nil
+      assert_raise(ArgumentError) {
+        @request.answer(true, nil, nil)
+      }
+    end
+
+    def test_answerAllowForgotEndpoint
+      @request.op_endpoint = nil
+      assert_raise(RuntimeError) {
+        @request.answer(true)
+      }
+    end
+
+    def test_checkIDWithNoIdentityOpenID1
+      msg = Message.new(OPENID1_NS)
+      msg.set_arg(OPENID_NS, 'return_to', 'bogus')
+      msg.set_arg(OPENID_NS, 'trust_root', 'bogus')
+      msg.set_arg(OPENID_NS, 'mode', 'checkid_setup')
+      msg.set_arg(OPENID_NS, 'assoc_handle', 'bogus')
+
+      assert_raise(Server::ProtocolError) {
+        Server::CheckIDRequest.from_message(msg, @server)
+      }
+    end
+
+    def test_fromMessageClaimedIDWithoutIdentityOpenID2
+      msg = Message.new(OPENID2_NS)
+      msg.set_arg(OPENID_NS, 'mode', 'checkid_setup')
+      msg.set_arg(OPENID_NS, 'return_to', 'http://invalid:8000/rt')
+      msg.set_arg(OPENID_NS, 'claimed_id', 'https://example.myopenid.com')
+
+      assert_raise(Server::ProtocolError) {
+        Server::CheckIDRequest.from_message(msg, @server)
+      }
+    end
+
+    def test_fromMessageIdentityWithoutClaimedIDOpenID2
+      msg = Message.new(OPENID2_NS)
+      msg.set_arg(OPENID_NS, 'mode', 'checkid_setup')
+      msg.set_arg(OPENID_NS, 'return_to', 'http://invalid:8000/rt')
+      msg.set_arg(OPENID_NS, 'identity', 'https://example.myopenid.com')
+
+      assert_raise(Server::ProtocolError) {
+        Server::CheckIDRequest.from_message(msg, @server)
+      }
+    end
+
+    def test_fromMessageWithEmptyTrustRoot
+      return_to = 'http://some.url/foo?bar=baz'
+      msg = Message.from_post_args({
+              'openid.assoc_handle' => '{blah}{blah}{OZivdQ==}',
+              'openid.claimed_id' => 'http://delegated.invalid/',
+              'openid.identity' => 'http://op-local.example.com/',
+              'openid.mode' => 'checkid_setup',
+              'openid.ns' => 'http://openid.net/signon/1.0',
+              'openid.return_to' => return_to,
+              'openid.trust_root' => ''
+              });
+      result = Server::CheckIDRequest.from_message(msg, @server)
+      assert_equal(return_to, result.trust_root)
+    end
+
+    def test_trustRootOpenID1
+      # Ignore openid.realm in OpenID 1
+      msg = Message.new(OPENID1_NS)
+      msg.set_arg(OPENID_NS, 'mode', 'checkid_setup')
+      msg.set_arg(OPENID_NS, 'trust_root', 'http://trustroot.com/')
+      msg.set_arg(OPENID_NS, 'realm', 'http://fake_trust_root/')
+      msg.set_arg(OPENID_NS, 'return_to', 'http://trustroot.com/foo')
+      msg.set_arg(OPENID_NS, 'assoc_handle', 'bogus')
+      msg.set_arg(OPENID_NS, 'identity', 'george')
+
+      result = Server::CheckIDRequest.from_message(msg, @server.op_endpoint)
+
+      assert(result.trust_root == 'http://trustroot.com/')
+    end
+
+    def test_trustRootOpenID2
+      # Ignore openid.trust_root in OpenID 2
+      msg = Message.new(OPENID2_NS)
+      msg.set_arg(OPENID_NS, 'mode', 'checkid_setup')
+      msg.set_arg(OPENID_NS, 'realm', 'http://trustroot.com/')
+      msg.set_arg(OPENID_NS, 'trust_root', 'http://fake_trust_root/')
+      msg.set_arg(OPENID_NS, 'return_to', 'http://trustroot.com/foo')
+      msg.set_arg(OPENID_NS, 'assoc_handle', 'bogus')
+      msg.set_arg(OPENID_NS, 'identity', 'george')
+      msg.set_arg(OPENID_NS, 'claimed_id', 'george')
+
+      result = Server::CheckIDRequest.from_message(msg, @server.op_endpoint)
+
+      assert(result.trust_root == 'http://trustroot.com/')
+    end
+
+    def test_answerAllowNoTrustRoot
+      @request.trust_root = nil
+      answer = @request.answer(true)
+      assert_equal(answer.request, @request)
+      _expectAnswer(answer, @request.identity)
+    end
+
+    def test_answerImmediateDenyOpenID2
+      # Look for mode=setup_needed in checkid_immediate negative
+      # response in OpenID 2 case.
+      #
+      # See specification Responding to Authentication Requests /
+      # Negative Assertions / In Response to Immediate Requests.
+      @request.mode = 'checkid_immediate'
+      @request.immediate = true
+
+      server_url = "http://setup-url.unittest/"
+      # crappiting setup_url, you dirty my interface with your presence!
+      answer = @request.answer(false, server_url)
+      assert_equal(answer.request, @request)
+      assert_equal(answer.fields.to_post_args.length, 3, answer.fields)
+      assert_equal(answer.fields.get_openid_namespace, OPENID2_NS)
+      assert_equal(answer.fields.get_arg(OPENID_NS, 'mode'),
+                   'setup_needed')
+      # user_setup_url no longer required.
+    end
+
+    def test_answerImmediateDenyOpenID1
+      # Look for user_setup_url in checkid_immediate negative response
+      # in OpenID 1 case.
+      @request.message = Message.new(OPENID1_NS)
+      @request.mode = 'checkid_immediate'
+      @request.immediate = true
+      @request.claimed_id = 'http://claimed-id.test/'
+      server_url = "http://setup-url.unittest/"
+      # crappiting setup_url, you dirty my interface with your presence!
+      answer = @request.answer(false, server_url)
+      assert_equal(answer.request, @request)
+      assert_equal(2, answer.fields.to_post_args.length, answer.fields)
+      assert_equal(OPENID1_NS, answer.fields.get_openid_namespace)
+      assert_equal('id_res', answer.fields.get_arg(OPENID_NS, 'mode'))
+
+      usu = answer.fields.get_arg(OPENID_NS, 'user_setup_url', '')
+      assert(usu.starts_with?(server_url))
+      expected_substr = 'openid.claimed_id=http%3A%2F%2Fclaimed-id.test%2F'
+      assert(!usu.index(expected_substr).nil?, usu)
+    end
+
+    def test_answerSetupDeny
+      answer = @request.answer(false)
+      assert_equal(answer.fields.get_args(OPENID_NS), {
+                     'mode' => 'cancel',
+                   })
+    end
+
+    def test_encodeToURL
+      server_url = 'http://openid-server.unittest/'
+      result = @request.encode_to_url(server_url)
+
+      # How to check?  How about a round-trip test.
+      base, result_args = result.split('?', 2)
+      result_args = Util.parse_query(result_args)
+      message = Message.from_post_args(result_args)
+      rebuilt_request = Server::CheckIDRequest.from_message(message,
+                                                    @server.op_endpoint)
+
+      @request.message = message
+
+      @request.instance_variables.each { |var|
+        assert_equal(@request.instance_variable_get(var),
+                     rebuilt_request.instance_variable_get(var), var)
+      }
+    end
+
+    def test_getCancelURL
+      url = @request.cancel_url
+      rt, query_string = url.split('?', -1)
+      assert_equal(@request.return_to, rt)
+      query = Util.parse_query(query_string)
+      assert_equal(query, {'openid.mode' => 'cancel',
+                     'openid.ns' => OPENID2_NS})
+    end
+
+    def test_getCancelURLimmed
+      @request.mode = 'checkid_immediate'
+      @request.immediate = true
+      assert_raise(ArgumentError) {
+        @request.cancel_url
+      }
+    end
+
+    def test_fromMessageWithoutTrustRoot
+        msg = Message.new(OPENID2_NS)
+        msg.set_arg(OPENID_NS, 'mode', 'checkid_setup')
+        msg.set_arg(OPENID_NS, 'return_to', 'http://real.trust.root/foo')
+        msg.set_arg(OPENID_NS, 'assoc_handle', 'bogus')
+        msg.set_arg(OPENID_NS, 'identity', 'george')
+        msg.set_arg(OPENID_NS, 'claimed_id', 'george')
+
+        result = Server::CheckIDRequest.from_message(msg, @server.op_endpoint)
+
+        assert_equal(result.trust_root, 'http://real.trust.root/foo')
+    end
+
+    def test_fromMessageWithoutTrustRootOrReturnTo
+        msg = Message.new(OPENID2_NS)
+        msg.set_arg(OPENID_NS, 'mode', 'checkid_setup')
+        msg.set_arg(OPENID_NS, 'assoc_handle', 'bogus')
+        msg.set_arg(OPENID_NS, 'identity', 'george')
+        msg.set_arg(OPENID_NS, 'claimed_id', 'george')
+
+        assert_raises(Server::ProtocolError) {
+          Server::CheckIDRequest.from_message(msg, @server.op_endpoint)
+        }
+    end
+  end
+
+  class TestCheckIDExtension < Test::Unit::TestCase
+
+    def setup
+      @op_endpoint = 'http://endpoint.unittest/ext'
+      @store = Store::Memory.new()
+      @server = Server::Server.new(@store, @op_endpoint)
+      @request = Server::CheckIDRequest.new(
+                                    'http://bambam.unittest/',
+                                    'http://bar.unittest/999',
+                                    @server.op_endpoint,
+                                    'http://bar.unittest/',
+                                    false)
+      @request.message = Message.new(OPENID2_NS)
+      @response = Server::OpenIDResponse.new(@request)
+      @response.fields.set_arg(OPENID_NS, 'mode', 'id_res')
+      @response.fields.set_arg(OPENID_NS, 'blue', 'star')
+    end
+
+    def test_addField
+      namespace = 'something:'
+      @response.fields.set_arg(namespace, 'bright', 'potato')
+      assert_equal(@response.fields.get_args(OPENID_NS),
+                   {'blue' => 'star',
+                     'mode' => 'id_res',
+                   })
+      
+      assert_equal(@response.fields.get_args(namespace),
+                   {'bright' => 'potato'})
+    end
+
+    def test_addFields
+      namespace = 'mi5:'
+      args =  {'tangy' => 'suspenders',
+        'bravo' => 'inclusion'}
+      @response.fields.update_args(namespace, args)
+      assert_equal(@response.fields.get_args(OPENID_NS),
+                   {'blue' => 'star',
+                     'mode' => 'id_res',
+                   })
+      assert_equal(@response.fields.get_args(namespace), args)
+    end
+  end
+
+  class MockSignatory
+    attr_accessor :isValid, :assocs
+
+    def initialize(assoc)
+      @isValid = true
+      @assocs = [assoc]
+    end
+
+    def verify(assoc_handle, message)
+      Util.assert(message.has_key?(OPENID_NS, "sig"))
+      if self.assocs.member?([true, assoc_handle])
+        return @isValid
+      else
+        return false
+      end
+    end
+
+    def get_association(assoc_handle, dumb)
+      if self.assocs.member?([dumb, assoc_handle])
+        # This isn't a valid implementation for many uses of this
+        # function, mind you.
+        return true
+      else
+        return nil
+      end
+    end
+
+    def invalidate(assoc_handle, dumb)
+      if self.assocs.member?([dumb, assoc_handle])
+        @assocs.delete([dumb, assoc_handle])
+      end
+    end
+  end
+
+  class TestCheckAuth < Test::Unit::TestCase
+    def setup
+      @assoc_handle = 'mooooooooo'
+      @message = Message.from_post_args({
+                                          'openid.sig' => 'signarture',
+                                          'one' => 'alpha',
+                                          'two' => 'beta',
+                                        })
+      @request = Server::CheckAuthRequest.new(
+                                      @assoc_handle, @message)
+      @request.message = Message.new(OPENID2_NS)
+
+      @signatory = MockSignatory.new([true, @assoc_handle])
+    end
+
+    def test_to_s
+      @request.to_s
+    end
+
+    def test_valid
+      r = @request.answer(@signatory)
+      assert_equal({'is_valid' => 'true'},
+                   r.fields.get_args(OPENID_NS))
+      assert_equal(r.request, @request)
+    end
+
+    def test_invalid
+      @signatory.isValid = false
+      r = @request.answer(@signatory)
+      assert_equal({'is_valid' => 'false'},
+                   r.fields.get_args(OPENID_NS))
+      
+    end
+
+    def test_replay
+      # Don't validate the same response twice.
+      #
+      # From "Checking the Nonce"::
+      #
+      #   When using "check_authentication", the OP MUST ensure that an
+      #   assertion has not yet been accepted with the same value for
+      #   "openid.response_nonce".
+      #
+      # In this implementation, the assoc_handle is only valid once.
+      # And nonces are a signed component of the message, so they can't
+      # be used with another handle without breaking the sig.
+      r = @request.answer(@signatory)
+      r = @request.answer(@signatory)
+      assert_equal({'is_valid' => 'false'},
+                   r.fields.get_args(OPENID_NS))
+    end
+
+    def test_invalidatehandle
+      @request.invalidate_handle = "bogusHandle"
+      r = @request.answer(@signatory)
+      assert_equal(r.fields.get_args(OPENID_NS),
+                   {'is_valid' => 'true',
+                     'invalidate_handle' => "bogusHandle"})
+      assert_equal(r.request, @request)
+    end
+
+    def test_invalidatehandleNo
+      assoc_handle = 'goodhandle'
+      @signatory.assocs << [false, 'goodhandle']
+      @request.invalidate_handle = assoc_handle
+      r = @request.answer(@signatory)
+      assert_equal(r.fields.get_args(OPENID_NS), {'is_valid' => 'true'})
+    end
+  end
+
+  class TestAssociate < Test::Unit::TestCase
+    # TODO: test DH with non-default values for modulus and gen.
+    # (important to do because we actually had it broken for a while.)
+
+    def setup
+      @request = Server::AssociateRequest.from_message(Message.from_post_args({}))
+      @store = Store::Memory.new()
+      @signatory = Server::Signatory.new(@store)
+    end
+
+    def test_dhSHA1
+      @assoc = @signatory.create_association(false, 'HMAC-SHA1')
+      consumer_dh = DiffieHellman.from_defaults()
+      cpub = consumer_dh.public
+      server_dh = DiffieHellman.from_defaults()
+      session = Server::DiffieHellmanSHA1ServerSession.new(server_dh, cpub)
+      @request = Server::AssociateRequest.new(session, 'HMAC-SHA1')
+      @request.message = Message.new(OPENID2_NS)
+      response = @request.answer(@assoc)
+      rfg = lambda { |f| response.fields.get_arg(OPENID_NS, f) }
+      assert_equal(rfg.call("assoc_type"), "HMAC-SHA1")
+      assert_equal(rfg.call("assoc_handle"), @assoc.handle)
+      assert(!rfg.call("mac_key"))
+      assert_equal(rfg.call("session_type"), "DH-SHA1")
+      assert(rfg.call("enc_mac_key"))
+      assert(rfg.call("dh_server_public"))
+
+      enc_key = Util.from_base64(rfg.call("enc_mac_key"))
+      spub = CryptUtil.base64_to_num(rfg.call("dh_server_public"))
+      secret = consumer_dh.xor_secret(CryptUtil.method('sha1'),
+                                      spub, enc_key)
+      assert_equal(secret, @assoc.secret)
+    end
+
+    def test_dhSHA256
+      @assoc = @signatory.create_association(false, 'HMAC-SHA256')
+      consumer_dh = DiffieHellman.from_defaults()
+      cpub = consumer_dh.public
+      server_dh = DiffieHellman.from_defaults()
+      session = Server::DiffieHellmanSHA256ServerSession.new(server_dh, cpub)
+      @request = Server::AssociateRequest.new(session, 'HMAC-SHA256')
+      @request.message = Message.new(OPENID2_NS)
+      response = @request.answer(@assoc)
+      rfg = lambda { |f| response.fields.get_arg(OPENID_NS, f) }
+      assert_equal(rfg.call("assoc_type"), "HMAC-SHA256")
+      assert_equal(rfg.call("assoc_handle"), @assoc.handle)
+      assert(!rfg.call("mac_key"))
+      assert_equal(rfg.call("session_type"), "DH-SHA256")
+      assert(rfg.call("enc_mac_key"))
+      assert(rfg.call("dh_server_public"))
+
+      enc_key = Util.from_base64(rfg.call("enc_mac_key"))
+      spub = CryptUtil.base64_to_num(rfg.call("dh_server_public"))
+      secret = consumer_dh.xor_secret(CryptUtil.method('sha256'),
+                                      spub, enc_key)
+      assert_equal(secret, @assoc.secret)
+    end
+
+    def test_protoError256
+      s256_session = Consumer::DiffieHellmanSHA256Session.new()
+
+      invalid_s256 = {'openid.assoc_type' => 'HMAC-SHA1',
+        'openid.session_type' => 'DH-SHA256',}
+      invalid_s256.merge!(s256_session.get_request())
+
+      invalid_s256_2 = {'openid.assoc_type' => 'MONKEY-PIRATE',
+        'openid.session_type' => 'DH-SHA256',}
+      invalid_s256_2.merge!(s256_session.get_request())
+
+      bad_request_argss = [
+                           invalid_s256,
+                           invalid_s256_2,
+                          ]
+
+      bad_request_argss.each { |request_args|
+        message = Message.from_post_args(request_args)
+        assert_raise(Server::ProtocolError) {
+          Server::AssociateRequest.from_message(message)
+        }
+      }
+    end
+
+    def test_protoError
+      s1_session = Consumer::DiffieHellmanSHA1Session.new()
+
+      invalid_s1 = {'openid.assoc_type' => 'HMAC-SHA256',
+        'openid.session_type' => 'DH-SHA1',}
+      invalid_s1.merge!(s1_session.get_request())
+
+      invalid_s1_2 = {'openid.assoc_type' => 'ROBOT-NINJA',
+        'openid.session_type' => 'DH-SHA1',}
+      invalid_s1_2.merge!(s1_session.get_request())
+
+      bad_request_argss = [
+                           {'openid.assoc_type' => 'Wha?'},
+                           invalid_s1,
+                           invalid_s1_2,
+                          ]
+            
+      bad_request_argss.each { |request_args|
+        message = Message.from_post_args(request_args)
+        assert_raise(Server::ProtocolError) {
+          Server::AssociateRequest.from_message(message)
+        }
+      }
+    end
+
+    def test_protoErrorFields
+
+      contact = 'user@example.invalid'
+      reference = 'Trac ticket number MAX_INT'
+      error = 'poltergeist'
+
+      openid1_args = {
+        'openid.identitiy' => 'invalid',
+        'openid.mode' => 'checkid_setup',
+      }
+
+      openid2_args = openid1_args.dup
+      openid2_args.merge!({'openid.ns' => OPENID2_NS})
+
+      # Check presence of optional fields in both protocol versions
+
+      openid1_msg = Message.from_post_args(openid1_args)
+      p = Server::ProtocolError.new(openid1_msg, error,
+                                    reference, contact)
+      reply = p.to_message()
+
+      assert_equal(reply.get_arg(OPENID_NS, 'reference'), reference)
+      assert_equal(reply.get_arg(OPENID_NS, 'contact'), contact)
+
+      openid2_msg = Message.from_post_args(openid2_args)
+      p = Server::ProtocolError.new(openid2_msg, error,
+                                    reference, contact)
+      reply = p.to_message()
+
+      assert_equal(reply.get_arg(OPENID_NS, 'reference'), reference)
+      assert_equal(reply.get_arg(OPENID_NS, 'contact'), contact)
+    end
+
+    def failUnlessExpiresInMatches(msg, expected_expires_in)
+      expires_in_str = msg.get_arg(OPENID_NS, 'expires_in', NO_DEFAULT)
+      expires_in = expires_in_str.to_i
+
+      # Slop is necessary because the tests can sometimes get run
+      # right on a second boundary
+      slop = 1 # second
+      difference = expected_expires_in - expires_in
+
+      error_message = sprintf('"expires_in" value not within %s of expected: ' +
+                              'expected=%s, actual=%s', slop, expected_expires_in,
+                              expires_in)
+      assert((0 <= difference and difference <= slop), error_message)
+    end
+
+    def test_plaintext
+      @assoc = @signatory.create_association(false, 'HMAC-SHA1')
+      response = @request.answer(@assoc)
+      rfg = lambda { |f| response.fields.get_arg(OPENID_NS, f) }
+
+      assert_equal(rfg.call("assoc_type"), "HMAC-SHA1")
+      assert_equal(rfg.call("assoc_handle"), @assoc.handle)
+
+      failUnlessExpiresInMatches(response.fields,
+                                 @signatory.secret_lifetime)
+
+      assert_equal(
+                   rfg.call("mac_key"), Util.to_base64(@assoc.secret))
+      assert(!rfg.call("session_type"))
+      assert(!rfg.call("enc_mac_key"))
+      assert(!rfg.call("dh_server_public"))
+    end
+
+    def test_plaintext_v2
+        # The main difference between this and the v1 test is that
+        # session_type is always returned in v2.
+        args = {
+            'openid.ns' => OPENID2_NS,
+            'openid.mode' => 'associate',
+            'openid.assoc_type' => 'HMAC-SHA1',
+            'openid.session_type' => 'no-encryption',
+            }
+        @request = Server::AssociateRequest.from_message(
+          Message.from_post_args(args))
+
+        assert(!@request.message.is_openid1())
+
+        @assoc = @signatory.create_association(false, 'HMAC-SHA1')
+        response = @request.answer(@assoc)
+        rfg = lambda { |f| response.fields.get_arg(OPENID_NS, f) }
+
+        assert_equal(rfg.call("assoc_type"), "HMAC-SHA1")
+        assert_equal(rfg.call("assoc_handle"), @assoc.handle)
+
+        failUnlessExpiresInMatches(
+            response.fields, @signatory.secret_lifetime)
+
+        assert_equal(
+            rfg.call("mac_key"), Util.to_base64(@assoc.secret))
+
+        assert_equal(rfg.call("session_type"), "no-encryption")
+        assert(!rfg.call("enc_mac_key"))
+        assert(!rfg.call("dh_server_public"))
+    end
+
+    def test_plaintext256
+      @assoc = @signatory.create_association(false, 'HMAC-SHA256')
+      response = @request.answer(@assoc)
+      rfg = lambda { |f| response.fields.get_arg(OPENID_NS, f) }
+
+      assert_equal(rfg.call("assoc_type"), "HMAC-SHA1")
+      assert_equal(rfg.call("assoc_handle"), @assoc.handle)
+
+      failUnlessExpiresInMatches(
+                                 response.fields, @signatory.secret_lifetime)
+
+      assert_equal(
+                   rfg.call("mac_key"), Util.to_base64(@assoc.secret))
+      assert(!rfg.call("session_type"))
+      assert(!rfg.call("enc_mac_key"))
+      assert(!rfg.call("dh_server_public"))
+    end
+
+    def test_unsupportedPrefer
+      allowed_assoc = 'COLD-PET-RAT'
+      allowed_sess = 'FROG-BONES'
+      message = 'This is a unit test'
+
+      # Set an OpenID 2 message so answerUnsupported doesn't raise
+      # ProtocolError.
+      @request.message = Message.new(OPENID2_NS)
+
+      response = @request.answer_unsupported(message,
+                                             allowed_assoc,
+                                             allowed_sess)
+      rfg = lambda { |f| response.fields.get_arg(OPENID_NS, f) }
+      assert_equal(rfg.call('error_code'), 'unsupported-type')
+      assert_equal(rfg.call('assoc_type'), allowed_assoc)
+      assert_equal(rfg.call('error'), message)
+      assert_equal(rfg.call('session_type'), allowed_sess)
+    end
+
+    def test_unsupported
+      message = 'This is a unit test'
+
+      # Set an OpenID 2 message so answerUnsupported doesn't raise
+      # ProtocolError.
+      @request.message = Message.new(OPENID2_NS)
+
+      response = @request.answer_unsupported(message)
+      rfg = lambda { |f| response.fields.get_arg(OPENID_NS, f) }
+      assert_equal(rfg.call('error_code'), 'unsupported-type')
+      assert_equal(rfg.call('assoc_type'), nil)
+      assert_equal(rfg.call('error'), message)
+      assert_equal(rfg.call('session_type'), nil)
+    end
+
+    def test_openid1_unsupported_explode
+      # answer_unsupported on an associate request should explode if
+      # the request was an OpenID 1 request.
+      m = Message.new(OPENID1_NS)
+
+      assert_raise(Server::ProtocolError) {
+        @request.answer_unsupported(m)
+      }
+    end
+  end
+
+  class Counter
+    def initialize
+      @count = 0
+    end
+
+    def inc
+      @count += 1
+    end
+  end
+
+  class UnhandledError < Exception
+  end
+
+  class TestServer < Test::Unit::TestCase
+    include TestUtil
+
+    def setup
+      @store = Store::Memory.new()
+      @server = Server::Server.new(@store, "http://server.unittest/endpt")
+      # catchlogs_setup()
+    end
+
+    def test_failed_dispatch
+      request = Server::OpenIDRequest.new()
+      request.mode = "monkeymode"
+      request.message = Message.new(OPENID1_NS)
+      assert_raise(RuntimeError) {
+        webresult = @server.handle_request(request)
+      }
+    end
+
+    def test_decode_request
+      @server.decoder = BogusDecoder.new(@server)
+      assert(@server.decode_request({}) == "BOGUS")
+    end
+
+    def test_encode_response
+      @server.encoder = BogusEncoder.new
+      assert(@server.encode_response(nil) == "BOGUS")
+    end
+
+    def test_dispatch
+      monkeycalled = Counter.new()
+
+      @server.extend(InstanceDefExtension)
+      @server.instance_def(:openid_monkeymode) do |request|
+        raise UnhandledError
+      end
+
+      request = Server::OpenIDRequest.new()
+      request.mode = "monkeymode"
+      request.message = Message.new(OPENID1_NS)
+      assert_raise(UnhandledError) {
+        webresult = @server.handle_request(request)
+      }
+    end
+
+    def test_associate
+      request = Server::AssociateRequest.from_message(Message.from_post_args({}))
+      response = @server.openid_associate(request)
+      assert(response.fields.has_key?(OPENID_NS, "assoc_handle"),
+             sprintf("No assoc_handle here: %s", response.fields.inspect))
+    end
+
+    def test_associate2
+      # Associate when the server has no allowed association types
+      #
+      # Gives back an error with error_code and no fallback session or
+      # assoc types.
+      @server.negotiator.allowed_types = []
+
+      # Set an OpenID 2 message so answerUnsupported doesn't raise
+      # ProtocolError.
+      msg = Message.from_post_args({
+                                     'openid.ns' => OPENID2_NS,
+                                     'openid.session_type' => 'no-encryption',
+                                   })
+
+      request = Server::AssociateRequest.from_message(msg)
+
+      response = @server.openid_associate(request)
+      assert(response.fields.has_key?(OPENID_NS, "error"))
+      assert(response.fields.has_key?(OPENID_NS, "error_code"))
+      assert(!response.fields.has_key?(OPENID_NS, "assoc_handle"))
+      assert(!response.fields.has_key?(OPENID_NS, "assoc_type"))
+      assert(!response.fields.has_key?(OPENID_NS, "session_type"))
+    end
+
+    def test_associate3
+      # Request an assoc type that is not supported when there are
+      # supported types.
+      #
+      # Should give back an error message with a fallback type.
+      @server.negotiator.allowed_types = [['HMAC-SHA256', 'DH-SHA256']]
+
+      msg = Message.from_post_args({
+                                     'openid.ns' => OPENID2_NS,
+                                     'openid.session_type' => 'no-encryption',
+                                   })
+
+      request = Server::AssociateRequest.from_message(msg)
+      response = @server.openid_associate(request)
+
+      assert(response.fields.has_key?(OPENID_NS, "error"))
+      assert(response.fields.has_key?(OPENID_NS, "error_code"))
+      assert(!response.fields.has_key?(OPENID_NS, "assoc_handle"))
+
+      assert_equal(response.fields.get_arg(OPENID_NS, "assoc_type"),
+                   'HMAC-SHA256')
+      assert_equal(response.fields.get_arg(OPENID_NS, "session_type"),
+                   'DH-SHA256')
+    end
+
+    def test_associate4
+      # DH-SHA256 association session
+      @server.negotiator.allowed_types = [['HMAC-SHA256', 'DH-SHA256']]
+
+      query = {
+        'openid.dh_consumer_public' =>
+        'ALZgnx8N5Lgd7pCj8K86T/DDMFjJXSss1SKoLmxE72kJTzOtG6I2PaYrHX' +
+        'xku4jMQWSsGfLJxwCZ6280uYjUST/9NWmuAfcrBfmDHIBc3H8xh6RBnlXJ' +
+        '1WxJY3jHd5k1/ZReyRZOxZTKdF/dnIqwF8ZXUwI6peV0TyS/K1fOfF/s',
+
+        'openid.assoc_type' => 'HMAC-SHA256',
+        'openid.session_type' => 'DH-SHA256',
+      }
+
+      message = Message.from_post_args(query)
+      request = Server::AssociateRequest.from_message(message)
+      response = @server.openid_associate(request)
+      assert(response.fields.has_key?(OPENID_NS, "assoc_handle"))
+    end
+
+    def test_no_encryption_openid1
+      # Make sure no-encryption associate requests for OpenID 1 are
+      # logged.
+      assert_log_matches(/Continuing anyway./) {
+        m = Message.from_openid_args({
+                                       'session_type' => 'no-encryption',
+                                     })
+
+        req = Server::AssociateRequest.from_message(m)
+      }
+    end
+
+    def test_missingSessionTypeOpenID2
+      # Make sure session_type is required in OpenID 2
+      msg = Message.from_post_args({
+                                     'openid.ns' => OPENID2_NS,
+                                   })
+
+      assert_raises(Server::ProtocolError) {
+        Server::AssociateRequest.from_message(msg)
+      }
+    end
+
+    def test_checkAuth
+      request = Server::CheckAuthRequest.new('arrrrrf', '0x3999', [])
+      request.message = Message.new(OPENID2_NS)
+      response = nil
+      silence_logging {
+        response = @server.openid_check_authentication(request)
+      }
+      assert(response.fields.has_key?(OPENID_NS, "is_valid"))
+    end
+  end
+
+  class TestingRequest < Server::OpenIDRequest
+    attr_accessor :assoc_handle, :namespace
+  end
+
+  class TestSignatory < Test::Unit::TestCase
+    include TestUtil
+
+    def setup
+      @store = Store::Memory.new()
+      @signatory = Server::Signatory.new(@store)
+      @_dumb_key = @signatory.class._dumb_key
+      @_normal_key = @signatory.class._normal_key
+      # CatchLogs.setUp(self)
+    end
+
+    def test_get_association_nil
+      assert_raises(ArgumentError) {
+        @signatory.get_association(nil, false)
+      }
+    end
+
+    def test_sign
+      request = TestingRequest.new()
+      assoc_handle = '{assoc}{lookatme}'
+      @store.store_association(
+                               @_normal_key,
+                               Association.from_expires_in(60, assoc_handle,
+                                                           'sekrit', 'HMAC-SHA1'))
+      request.assoc_handle = assoc_handle
+      request.namespace = OPENID1_NS
+      response = Server::OpenIDResponse.new(request)
+      response.fields = Message.from_openid_args({
+                                                   'foo' => 'amsigned',
+                                                   'bar' => 'notsigned',
+                                                   'azu' => 'alsosigned',
+                                                 })
+      sresponse = @signatory.sign(response)
+      assert_equal(
+            sresponse.fields.get_arg(OPENID_NS, 'assoc_handle'),
+            assoc_handle)
+      assert_equal(sresponse.fields.get_arg(OPENID_NS, 'signed'),
+                   'assoc_handle,azu,bar,foo,signed')
+      assert(sresponse.fields.get_arg(OPENID_NS, 'sig'))
+      # assert(!@messages, @messages)
+    end
+
+    def test_signDumb
+      request = TestingRequest.new()
+      request.assoc_handle = nil
+      request.namespace = OPENID2_NS
+      response = Server::OpenIDResponse.new(request)
+      response.fields = Message.from_openid_args({
+                                                   'foo' => 'amsigned',
+                                                   'bar' => 'notsigned',
+                                                   'azu' => 'alsosigned',
+                                                   'ns' => OPENID2_NS,
+                                                 })
+      sresponse = @signatory.sign(response)
+      assoc_handle = sresponse.fields.get_arg(OPENID_NS, 'assoc_handle')
+      assert(assoc_handle)
+      assoc = @signatory.get_association(assoc_handle, true)
+      assert(assoc)
+      assert_equal(sresponse.fields.get_arg(OPENID_NS, 'signed'),
+                   'assoc_handle,azu,bar,foo,ns,signed')
+      assert(sresponse.fields.get_arg(OPENID_NS, 'sig'))
+      # assert(!@messages, @messages)
+    end
+
+    def test_signExpired
+      # Sign a response to a message with an expired handle (using
+      # invalidate_handle).
+      #
+      # From "Verifying with an Association":
+      #
+      #   If an authentication request included an association handle
+      #   for an association between the OP and the Relying party, and
+      #   the OP no longer wishes to use that handle (because it has
+      #   expired or the secret has been compromised, for instance),
+      #   the OP will send a response that must be verified directly
+      #   with the OP, as specified in Section 11.3.2. In that
+      #   instance, the OP will include the field
+      #   "openid.invalidate_handle" set to the association handle
+      #   that the Relying Party included with the original request.
+      request = TestingRequest.new()
+      request.namespace = OPENID2_NS
+      assoc_handle = '{assoc}{lookatme}'
+      @store.store_association(
+                               @_normal_key,
+                               Association.from_expires_in(-10, assoc_handle,
+                                                           'sekrit', 'HMAC-SHA1'))
+      assert(@store.get_association(@_normal_key, assoc_handle))
+
+      request.assoc_handle = assoc_handle
+      response = Server::OpenIDResponse.new(request)
+      response.fields = Message.from_openid_args({
+                                                   'foo' => 'amsigned',
+                                                   'bar' => 'notsigned',
+                                                   'azu' => 'alsosigned',
+                                                 })
+      sresponse = nil
+      silence_logging {
+        sresponse = @signatory.sign(response)
+      }
+
+      new_assoc_handle = sresponse.fields.get_arg(OPENID_NS, 'assoc_handle')
+      assert(new_assoc_handle)
+      assert(new_assoc_handle != assoc_handle)
+
+      assert_equal(
+            sresponse.fields.get_arg(OPENID_NS, 'invalidate_handle'),
+            assoc_handle)
+
+      assert_equal(sresponse.fields.get_arg(OPENID_NS, 'signed'),
+                   'assoc_handle,azu,bar,foo,invalidate_handle,signed')
+      assert(sresponse.fields.get_arg(OPENID_NS, 'sig'))
+
+      # make sure the expired association is gone
+      assert(!@store.get_association(@_normal_key, assoc_handle),
+             "expired association is still retrievable.")
+
+      # make sure the new key is a dumb mode association
+      assert(@store.get_association(@_dumb_key, new_assoc_handle))
+      assert(!@store.get_association(@_normal_key, new_assoc_handle))
+      # assert(@messages)
+    end
+
+    def test_signInvalidHandle
+      request = TestingRequest.new()
+      request.namespace = OPENID2_NS
+      assoc_handle = '{bogus-assoc}{notvalid}'
+
+      request.assoc_handle = assoc_handle
+      response = Server::OpenIDResponse.new(request)
+      response.fields = Message.from_openid_args({
+                                                   'foo' => 'amsigned',
+                                                   'bar' => 'notsigned',
+                                                   'azu' => 'alsosigned',
+                                                 })
+      sresponse = @signatory.sign(response)
+
+      new_assoc_handle = sresponse.fields.get_arg(OPENID_NS, 'assoc_handle')
+      assert(new_assoc_handle)
+      assert(new_assoc_handle != assoc_handle)
+
+      assert_equal(
+            sresponse.fields.get_arg(OPENID_NS, 'invalidate_handle'),
+            assoc_handle)
+
+      assert_equal(
+            sresponse.fields.get_arg(OPENID_NS, 'signed'),
+                   'assoc_handle,azu,bar,foo,invalidate_handle,signed')
+      assert(sresponse.fields.get_arg(OPENID_NS, 'sig'))
+
+      # make sure the new key is a dumb mode association
+      assert(@store.get_association(@_dumb_key, new_assoc_handle))
+      assert(!@store.get_association(@_normal_key, new_assoc_handle))
+      # @failIf(@messages, @messages)
+    end
+
+    def test_verify
+      assoc_handle = '{vroom}{zoom}'
+      assoc = Association.from_expires_in(
+            60, assoc_handle, 'sekrit', 'HMAC-SHA1')
+
+      @store.store_association(@_dumb_key, assoc)
+
+      signed = Message.from_post_args({
+                                        'openid.foo' => 'bar',
+                                        'openid.apple' => 'orange',
+                                        'openid.assoc_handle' => assoc_handle,
+                                        'openid.signed' => 'apple,assoc_handle,foo,signed',
+                                        'openid.sig' => 'uXoT1qm62/BB09Xbj98TQ8mlBco=',
+                                      })
+
+      verified = @signatory.verify(assoc_handle, signed)
+      assert(verified)
+      # assert(!@messages, @messages)
+    end
+
+    def test_verifyBadSig
+      assoc_handle = '{vroom}{zoom}'
+      assoc = Association.from_expires_in(
+            60, assoc_handle, 'sekrit', 'HMAC-SHA1')
+
+      @store.store_association(@_dumb_key, assoc)
+
+      signed = Message.from_post_args({
+            'openid.foo' => 'bar',
+            'openid.apple' => 'orange',
+            'openid.assoc_handle' => assoc_handle,
+            'openid.signed' => 'apple,assoc_handle,foo,signed',
+            'openid.sig' => 'uXoT1qm62/BB09Xbj98TQ8mlBco=BOGUS'
+            })
+
+      verified = @signatory.verify(assoc_handle, signed)
+      # @failIf(@messages, @messages)
+      assert(!verified)
+    end
+
+    def test_verifyBadHandle
+      assoc_handle = '{vroom}{zoom}'
+      signed = Message.from_post_args({
+            'foo' => 'bar',
+            'apple' => 'orange',
+            'openid.sig' => "Ylu0KcIR7PvNegB/K41KpnRgJl0=",
+            })
+
+      verified = nil
+      silence_logging {
+        verified = @signatory.verify(assoc_handle, signed)
+      }
+
+      assert(!verified)
+      #assert(@messages)
+    end
+
+    def test_verifyAssocMismatch
+      # Attempt to validate sign-all message with a signed-list assoc.
+      assoc_handle = '{vroom}{zoom}'
+      assoc = Association.from_expires_in(
+            60, assoc_handle, 'sekrit', 'HMAC-SHA1')
+
+      @store.store_association(@_dumb_key, assoc)
+
+      signed = Message.from_post_args({
+            'foo' => 'bar',
+            'apple' => 'orange',
+            'openid.sig' => "d71xlHtqnq98DonoSgoK/nD+QRM=",
+            })
+
+      verified = nil
+      silence_logging {
+        verified = @signatory.verify(assoc_handle, signed)
+      }
+
+      assert(!verified)
+      #assert(@messages)
+    end
+
+    def test_getAssoc
+      assoc_handle = makeAssoc(true)
+      assoc = @signatory.get_association(assoc_handle, true)
+      assert(assoc)
+      assert_equal(assoc.handle, assoc_handle)
+      # @failIf(@messages, @messages)
+    end
+
+    def test_getAssocExpired
+      assoc_handle = makeAssoc(true, -10)
+      assoc = nil
+      silence_logging {
+        assoc = @signatory.get_association(assoc_handle, true)
+      }
+      assert(!assoc, assoc)
+      # assert(@messages)
+    end
+
+    def test_getAssocInvalid
+      ah = 'no-such-handle'
+      silence_logging {
+        assert_equal(
+                     @signatory.get_association(ah, false), nil)
+      }
+      # assert(!@messages, @messages)
+    end
+
+    def test_getAssocDumbVsNormal
+      # getAssociation(dumb=False) cannot get a dumb assoc
+      assoc_handle = makeAssoc(true)
+      silence_logging {
+        assert_equal(
+                     @signatory.get_association(assoc_handle, false), nil)
+      }
+      # @failIf(@messages, @messages)
+    end
+
+    def test_getAssocNormalVsDumb
+      # getAssociation(dumb=True) cannot get a shared assoc
+      #
+      # From "Verifying Directly with the OpenID Provider"::
+      #
+      #   An OP MUST NOT verify signatures for associations that have shared
+      #   MAC keys.
+      assoc_handle = makeAssoc(false)
+      silence_logging {
+        assert_equal(
+                     @signatory.get_association(assoc_handle, true), nil)
+      }
+      # @failIf(@messages, @messages)
+    end
+
+    def test_createAssociation
+      assoc = @signatory.create_association(false)
+      silence_logging {
+        assert(@signatory.get_association(assoc.handle, false))
+      }
+      # @failIf(@messages, @messages)
+    end
+
+    def makeAssoc(dumb, lifetime=60)
+      assoc_handle = '{bling}'
+      assoc = Association.from_expires_in(lifetime, assoc_handle,
+                                          'sekrit', 'HMAC-SHA1')
+
+      silence_logging {
+        @store.store_association(((dumb and @_dumb_key) or @_normal_key), assoc)
+      }
+
+      return assoc_handle
+    end
+
+    def test_invalidate
+      assoc_handle = '-squash-'
+      assoc = Association.from_expires_in(60, assoc_handle,
+                                          'sekrit', 'HMAC-SHA1')
+
+      silence_logging {
+        @store.store_association(@_dumb_key, assoc)
+        assoc = @signatory.get_association(assoc_handle, true)
+        assert(assoc)
+        assoc = @signatory.get_association(assoc_handle, true)
+        assert(assoc)
+        @signatory.invalidate(assoc_handle, true)
+        assoc = @signatory.get_association(assoc_handle, true)
+        assert(!assoc)
+      }
+      # @failIf(@messages, @messages)
+    end
+  end
+
+  class RunthroughTestCase < Test::Unit::TestCase
+    def setup
+      @store = Store::Memory.new
+      @server = Server::Server.new(@store, "http://example.com/openid/server")
+    end
+
+    def test_openid1_assoc_checkid
+      assoc_args = {'openid.mode' => 'associate',
+                    'openid.assoc_type' => 'HMAC-SHA1'}
+      areq = @server.decode_request(assoc_args)
+      aresp = @server.handle_request(areq)
+      
+      amess = aresp.fields
+      assert(amess.is_openid1)
+      ahandle = amess.get_arg(OPENID_NS, 'assoc_handle')
+      assert(ahandle)
+      assoc = @store.get_association('http://localhost/|normal', ahandle)
+      assert(assoc.is_a?(Association))
+
+
+      checkid_args = {'openid.mode' => 'checkid_setup',
+                      'openid.return_to' => 'http://example.com/openid/consumer',
+                      'openid.assoc_handle' => ahandle,
+                      'openid.identity' => 'http://foo.com/'}
+      
+      cireq = @server.decode_request(checkid_args)
+      ciresp = cireq.answer(true)
+
+      signed_resp = @server.signatory.sign(ciresp)
+
+      assert_equal(assoc.get_message_signature(signed_resp.fields),
+                   signed_resp.fields.get_arg(OPENID_NS, 'sig'))
+                   
+      assert(assoc.check_message_signature(signed_resp.fields))
+    end
+
+  end
+end
diff --git a/vendor/gems/ruby-openid-2.1.4/test/test_sreg.rb b/vendor/gems/ruby-openid-2.1.4/test/test_sreg.rb
new file mode 100644 (file)
index 0000000..7438d59
--- /dev/null
@@ -0,0 +1,479 @@
+require 'openid/extensions/sreg'
+require 'openid/message'
+require 'openid/server'
+require 'test/unit'
+
+module OpenID
+  module SReg
+    module SRegTest
+      SOME_DATA = {
+        'nickname'=>'linusaur',
+        'postcode'=>'12345',
+        'country'=>'US',
+        'gender'=>'M',
+        'fullname'=>'Leonhard Euler',
+        'email'=>'president@whitehouse.gov',
+        'dob'=>'0000-00-00',
+        'language'=>'en-us',
+      }
+
+      class SRegTest < Test::Unit::TestCase
+
+        def test_is11
+          assert_equal(NS_URI, NS_URI_1_1)
+        end
+
+        def test_check_field_name
+          DATA_FIELDS.keys.each{|field_name|
+            OpenID::check_sreg_field_name(field_name)
+          }
+          assert_raises(ArgumentError) { OpenID::check_sreg_field_name('invalid') }
+          assert_raises(ArgumentError) { OpenID::check_sreg_field_name(nil) }
+        end
+
+        def test_unsupported
+          endpoint = FakeEndpoint.new([])
+          assert(!OpenID::supports_sreg?(endpoint))
+          assert_equal([NS_URI_1_1,NS_URI_1_0], endpoint.checked_uris)
+        end
+
+        def test_supported_1_1
+          endpoint = FakeEndpoint.new([NS_URI_1_1])
+          assert(OpenID::supports_sreg?(endpoint))
+          assert_equal([NS_URI_1_1], endpoint.checked_uris)
+        end
+
+        def test_supported_1_0
+          endpoint = FakeEndpoint.new([NS_URI_1_0])
+          assert(OpenID::supports_sreg?(endpoint))
+          assert_equal([NS_URI_1_1,NS_URI_1_0], endpoint.checked_uris)
+        end
+
+      end
+
+      class FakeEndpoint < Object
+        attr_accessor :checked_uris
+        def initialize(supported)
+          @supported = supported
+          @checked_uris = []
+        end
+
+        def uses_extension(namespace_uri)
+          @checked_uris << namespace_uri
+          return @supported.member?(namespace_uri)
+        end
+      end
+
+      class FakeMessage < Object
+        attr_accessor :namespaces
+        attr_accessor :openid1
+        def initialize
+          @openid1 = false
+          @namespaces = NamespaceMap.new
+        end
+        
+        def is_openid1
+          return @openid1
+        end
+
+      end
+
+      class GetNSTest < Test::Unit::TestCase
+        def setup
+          @msg = FakeMessage.new
+        end
+
+        def test_openid2_empty
+          ns_uri = OpenID::get_sreg_ns(@msg)
+          assert_equal('sreg', @msg.namespaces.get_alias(ns_uri))
+          assert_equal(NS_URI, ns_uri)
+        end
+
+        def test_openid1_empty
+          @msg.openid1 = true
+          ns_uri = OpenID::get_sreg_ns(@msg)
+          assert_equal('sreg', @msg.namespaces.get_alias(ns_uri))
+          assert_equal(NS_URI, ns_uri)
+        end
+        
+        def test_openid1defined_1_0
+          @msg.openid1 = true
+          @msg.namespaces.add(NS_URI_1_0)
+          ns_uri = OpenID::get_sreg_ns(@msg)
+          assert_equal(NS_URI_1_0, ns_uri)
+        end
+
+        def test_openid1_defined_1_0_override_alias
+          [true, false].each{|openid_version|
+            [NS_URI_1_0, NS_URI_1_1].each{|sreg_version|
+              ['sreg', 'bogus'].each{|name|
+                setup
+                @msg.openid1 = openid_version
+                @msg.namespaces.add_alias(sreg_version, name)
+                ns_uri = OpenID::get_sreg_ns(@msg)
+                assert_equal(name, @msg.namespaces.get_alias(ns_uri))
+                assert_equal(sreg_version, ns_uri)
+              }
+            }
+          }
+        end
+        
+        def test_openid1_defined_badly
+          @msg.openid1 = true
+          @msg.namespaces.add_alias('http://invalid/', 'sreg')
+          assert_raises(NamespaceError) { OpenID::get_sreg_ns(@msg) }
+        end
+
+        def test_openid2_defined_badly
+          @msg.namespaces.add_alias('http://invalid/', 'sreg')
+          assert_raises(NamespaceError) { OpenID::get_sreg_ns(@msg) }
+        end
+
+        def test_openid2_defined_1_0
+          @msg.namespaces.add(NS_URI_1_0)
+          ns_uri = OpenID::get_sreg_ns(@msg)
+          assert_equal(NS_URI_1_0, ns_uri)
+        end
+
+        def test_openid1_sreg_ns_from_args
+          args = {
+            'sreg.optional'=> 'nickname',
+            'sreg.required'=> 'dob',
+          }
+
+          m = Message.from_openid_args(args)
+
+          assert_equal('nickname', m.get_arg(NS_URI_1_1, 'optional'))
+          assert_equal('dob', m.get_arg(NS_URI_1_1, 'required'))
+        end
+
+      end
+
+      class SRegRequestTest < Test::Unit::TestCase
+        def test_construct_empty
+          req = Request.new
+          assert_equal([], req.optional)
+          assert_equal([], req.required)
+          assert_equal(nil, req.policy_url)
+          assert_equal(NS_URI, req.ns_uri)
+        end
+
+        def test_construct_fields
+          req = Request.new(['nickname'],['gender'],'http://policy', 'http://sreg.ns_uri')
+          assert_equal(['gender'], req.optional)
+          assert_equal(['nickname'], req.required)
+          assert_equal('http://policy', req.policy_url)
+          assert_equal('http://sreg.ns_uri', req.ns_uri)
+        end
+
+        def test_construct_bad_fields
+          assert_raises(ArgumentError) {Request.new(['elvis'])}
+        end
+
+        def test_from_openid_request_message_copied
+          message = Message.from_openid_args({"sreg.required" => "nickname"})
+          openid_req = Server::OpenIDRequest.new
+          openid_req.message = message
+          sreg_req = Request.from_openid_request(openid_req)
+          # check that the message is copied by looking at sreg namespace
+          assert_equal(NS_URI_1_1, message.namespaces.get_namespace_uri('sreg'))
+          assert_equal(NS_URI, sreg_req.ns_uri)
+          assert_equal(['nickname'], sreg_req.required)
+        end
+
+        def test_from_openid_request_ns_1_0
+          message = Message.from_openid_args({'ns.sreg' => NS_URI_1_0, 
+                                               "sreg.required" => "nickname"})
+          openid_req = Server::OpenIDRequest.new
+          openid_req.message = message
+          sreg_req = Request.from_openid_request(openid_req)
+          assert_equal(NS_URI_1_0, sreg_req.ns_uri)
+          assert_equal(['nickname'], sreg_req.required)
+        end
+
+        def test_from_openid_request_no_sreg
+          message = Message.new
+          openid_req = Server::OpenIDRequest.new
+          openid_req.message = message
+          sreg_req = Request.from_openid_request(openid_req)
+          assert(sreg_req.nil?)
+        end
+
+        def test_parse_extension_args_empty
+          req = Request.new
+          req.parse_extension_args({})
+        end
+
+        def test_parse_extension_args_extra_ignored
+          req = Request.new
+          req.parse_extension_args({'extra' => 'stuff'})
+        end
+
+        def test_parse_extension_args_non_strict
+          req = Request.new
+          req.parse_extension_args({'required' => 'stuff'})
+          assert_equal([], req.required)
+        end
+        
+        def test_parse_extension_args_strict
+          req = Request.new
+          assert_raises(ArgumentError) {
+            req.parse_extension_args({'required' => 'stuff'}, true)
+          }
+        end
+
+        def test_parse_extension_args_policy
+          req = Request.new
+          req.parse_extension_args({'policy_url' => 'http://policy'}, true)
+          assert_equal('http://policy', req.policy_url)
+        end
+
+        def test_parse_extension_args_required_empty
+          req = Request.new
+          req.parse_extension_args({'required' => ''}, true)
+          assert_equal([], req.required)
+        end
+
+        def test_parse_extension_args_optional_empty
+          req = Request.new
+          req.parse_extension_args({'optional' => ''},true)
+          assert_equal([], req.optional)
+        end
+
+        def test_parse_extension_args_optional_single
+          req = Request.new
+          req.parse_extension_args({'optional' => 'nickname'},true)
+          assert_equal(['nickname'], req.optional)
+        end
+
+        def test_parse_extension_args_optional_list
+          req = Request.new
+          req.parse_extension_args({'optional' => 'nickname,email'},true)
+          assert_equal(['nickname','email'], req.optional)
+        end
+
+        def test_parse_extension_args_optional_list_bad_nonstrict
+          req = Request.new
+          req.parse_extension_args({'optional' => 'nickname,email,beer'})
+          assert_equal(['nickname','email'], req.optional)
+        end
+
+        def test_parse_extension_args_optional_list_bad_strict
+          req = Request.new
+          assert_raises(ArgumentError) {
+            req.parse_extension_args({'optional' => 'nickname,email,beer'}, true)
+          }
+        end
+
+        def test_parse_extension_args_both_nonstrict
+          req = Request.new
+          req.parse_extension_args({'optional' => 'nickname', 'required' => 'nickname'})
+          assert_equal(['nickname'], req.required)
+          assert_equal([], req.optional)
+        end
+
+        def test_parse_extension_args_both_strict
+          req = Request.new
+          assert_raises(ArgumentError) {
+            req.parse_extension_args({'optional' => 'nickname', 'required' => 'nickname'},true)
+          }
+        end
+
+        def test_parse_extension_args_both_list
+          req = Request.new
+          req.parse_extension_args({'optional' => 'nickname,email', 'required' => 'country,postcode'},true)
+          assert_equal(['nickname','email'], req.optional)
+          assert_equal(['country','postcode'], req.required)
+        end
+
+        def test_all_requested_fields
+          req = Request.new
+          assert_equal([], req.all_requested_fields)
+          req.request_field('nickname')
+          assert_equal(['nickname'], req.all_requested_fields)
+          req.request_field('gender', true)
+          requested = req.all_requested_fields.sort
+          assert_equal(['gender', 'nickname'], requested)
+        end
+
+        def test_were_fields_requested
+          req = Request.new
+          assert(!req.were_fields_requested?)
+          req.request_field('nickname')
+          assert(req.were_fields_requested?)
+        end
+
+        def test_member
+          req = Request.new
+          DATA_FIELDS.keys.each {|f|
+            assert(!req.member?(f))
+          }
+          assert(!req.member?('something else'))
+          req.request_field('nickname')
+          DATA_FIELDS.keys.each {|f|
+            assert_equal(f == 'nickname',req.member?(f))
+          }
+        end
+
+        def test_request_field_bogus
+          req = Request.new
+          fields = DATA_FIELDS.keys
+          fields.each {|f| req.request_field(f) }
+          assert_equal(fields, req.optional)
+          assert_equal([], req.required)
+
+          # By default, adding the same fields over again has no effect
+          fields.each {|f| req.request_field(f) }
+          assert_equal(fields, req.optional)
+          assert_equal([], req.required)
+
+          # Requesting a field as required overrides requesting it as optional
+          expected = fields[1..-1]
+          overridden = fields[0]
+          req.request_field(overridden, true)
+          assert_equal(expected, req.optional)
+          assert_equal([overridden], req.required)
+
+          fields.each {|f| req.request_field(f, true) }
+          assert_equal(fields, req.required)
+          assert_equal([], req.optional)
+        end
+
+        def test_request_fields_type
+          req = Request.new
+          assert_raises(ArgumentError) { req.request_fields('nickname') }
+        end
+
+        def test_request_fields
+          req = Request.new
+          fields = DATA_FIELDS.keys
+
+          req.request_fields(fields)
+          assert_equal(fields, req.optional)
+          assert_equal([], req.required)
+          
+          # By default, adding the same fields over again has no effect
+          req.request_fields(fields)
+          assert_equal(fields, req.optional)
+          assert_equal([], req.required)
+
+          # required overrides optional
+          expected = fields[1..-1]
+          overridden = fields[0]
+          req.request_fields([overridden], true)
+          assert_equal(expected, req.optional)
+          assert_equal([overridden], req.required)
+
+          req.request_fields(fields, true)
+          assert_equal(fields, req.required)
+          assert_equal([], req.optional)
+
+          # optional does not override required
+          req.request_fields(fields)
+          assert_equal(fields, req.required)
+          assert_equal([], req.optional)
+        end
+
+        def test_get_extension_args
+          req = Request.new
+          assert_equal({}, req.get_extension_args)
+
+          req.request_field('nickname')
+          assert_equal({'optional' => 'nickname'}, req.get_extension_args)
+
+          req.request_field('email')
+          assert_equal({'optional' => 'nickname,email'}, req.get_extension_args)
+
+          req.request_field('gender', true)
+          assert_equal({'optional' => 'nickname,email',
+                         'required' => 'gender'}, req.get_extension_args)
+
+          req.request_field('dob', true)
+          assert_equal({'optional' => 'nickname,email',
+                         'required' => 'gender,dob'}, req.get_extension_args)
+
+          req.policy_url = 'http://policy'
+          assert_equal({'optional' => 'nickname,email',
+                         'required' => 'gender,dob',
+                         'policy_url' => 'http://policy'},
+                       req.get_extension_args)
+
+        end
+      end
+
+      class DummySuccessResponse
+        attr_accessor :message
+        def initialize(message, signed_stuff)
+          @message = message
+          @signed_stuff = signed_stuff
+        end
+        def get_signed_ns(ns_uri)
+          return @signed_stuff
+        end
+      end
+
+
+      class SRegResponseTest < Test::Unit::TestCase
+        def test_construct
+          resp = Response.new(SOME_DATA)
+          assert_equal(SOME_DATA, resp.get_extension_args)
+          assert_equal(NS_URI, resp.ns_uri)
+          resp2 = Response.new({}, "http://foo")
+          assert_equal({}, resp2.get_extension_args)
+          assert_equal('http://foo', resp2.ns_uri)
+        end
+
+        def test_from_success_response_signed
+          message = Message.from_openid_args({
+                                               'sreg.nickname'=>'The Mad Stork',
+                                             })
+          success_resp = DummySuccessResponse.new(message, {})
+          sreg_resp = Response.from_success_response(success_resp)
+          assert_equal({}, sreg_resp.get_extension_args)
+        end
+
+        def test_from_success_response_unsigned
+          message = Message.from_openid_args({
+                                               'ns.sreg' => NS_URI,
+                                               'sreg.nickname' => 'The Mad Stork',
+                                             })
+          success_resp = DummySuccessResponse.new(message, {})
+          sreg_resp = Response.from_success_response(success_resp, false)
+          assert_equal({'nickname' => 'The Mad Stork'}, 
+                       sreg_resp.get_extension_args)
+        end
+      end
+
+      class SendFieldsTest < Test::Unit::TestCase
+        # class SendFieldsTest < Object
+        def test_send_fields
+          # create a request message with simple reg fields
+          sreg_req = Request.new(['nickname', 'email'], ['fullname'])
+          req_msg = Message.new
+          req_msg.update_args(NS_URI, sreg_req.get_extension_args)
+          req = Server::OpenIDRequest.new
+          req.message = req_msg
+
+          # -> checkid_* request
+
+          # create a response
+          resp_msg = Message.new
+          resp = Server::OpenIDResponse.new(req)
+          resp.fields = resp_msg
+          sreg_resp = Response.extract_response(sreg_req, SOME_DATA)
+          resp.add_extension(sreg_resp)
+
+          # <- id_res response
+
+          # extract sent fields
+          sreg_data_resp = resp_msg.get_args(NS_URI)
+          assert_equal({'nickname' => 'linusaur',
+                         'email'=>'president@whitehouse.gov',
+                         'fullname'=>'Leonhard Euler',
+                       }, sreg_data_resp)
+        end
+      end
+    end
+  end
+end
+
diff --git a/vendor/gems/ruby-openid-2.1.4/test/test_stores.rb b/vendor/gems/ruby-openid-2.1.4/test/test_stores.rb
new file mode 100644 (file)
index 0000000..5910621
--- /dev/null
@@ -0,0 +1,269 @@
+require 'test/unit'
+require 'openid/store/interface'
+require 'openid/store/filesystem'
+require 'openid/store/memory'
+require 'openid/util'
+require 'openid/store/nonce'
+require 'openid/association'
+
+module OpenID
+  module Store
+    module StoreTestCase
+      @@allowed_handle = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~'
+      @@allowed_nonce = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
+      
+      def _gen_nonce
+        OpenID::CryptUtil.random_string(8, @@allowed_nonce)
+      end
+
+      def _gen_handle(n)
+        OpenID::CryptUtil.random_string(n, @@allowed_handle)
+      end
+
+      def _gen_secret(n, chars=nil)
+        OpenID::CryptUtil.random_string(n, chars)
+      end
+
+      def _gen_assoc(issued, lifetime=600)
+        secret = _gen_secret(20)
+        handle = _gen_handle(128)
+        OpenID::Association.new(handle, secret, Time.now + issued, lifetime,
+                                'HMAC-SHA1') 
+      end
+      
+      def _check_retrieve(url, handle=nil, expected=nil)
+        ret_assoc = @store.get_association(url, handle)
+
+        if expected.nil?
+          assert_nil(ret_assoc)
+        else
+          assert_equal(expected, ret_assoc)
+          assert_equal(expected.handle, ret_assoc.handle)
+          assert_equal(expected.secret, ret_assoc.secret)
+        end
+      end
+
+      def _check_remove(url, handle, expected)
+        present = @store.remove_association(url, handle)
+        assert_equal(expected, present)
+      end
+
+      def test_store
+        server_url = "http://www.myopenid.com/openid"
+        assoc = _gen_assoc(issued=0)
+
+        # Make sure that a missing association returns no result
+        _check_retrieve(server_url)
+
+        # Check that after storage, getting returns the same result
+        @store.store_association(server_url, assoc)
+        _check_retrieve(server_url, nil, assoc)
+
+        # more than once
+        _check_retrieve(server_url, nil, assoc)
+
+        # Storing more than once has no ill effect
+        @store.store_association(server_url, assoc)
+        _check_retrieve(server_url, nil, assoc)
+
+        # Removing an association that does not exist returns not present
+        _check_remove(server_url, assoc.handle + 'x', false)
+
+        # Removing an association that does not exist returns not present
+        _check_remove(server_url + 'x', assoc.handle, false)
+
+        # Removing an association that is present returns present
+        _check_remove(server_url, assoc.handle, true)
+
+        # but not present on subsequent calls
+        _check_remove(server_url, assoc.handle, false)
+
+        # Put assoc back in the store
+        @store.store_association(server_url, assoc)
+
+        # More recent and expires after assoc
+        assoc2 = _gen_assoc(issued=1)
+        @store.store_association(server_url, assoc2)
+
+        # After storing an association with a different handle, but the
+        # same server_url, the handle with the later expiration is returned.
+        _check_retrieve(server_url, nil, assoc2)
+
+        # We can still retrieve the older association
+        _check_retrieve(server_url, assoc.handle, assoc)
+
+        # Plus we can retrieve the association with the later expiration
+        # explicitly
+        _check_retrieve(server_url, assoc2.handle, assoc2)
+
+        # More recent, and expires earlier than assoc2 or assoc. Make sure
+        # that we're picking the one with the latest issued date and not
+        # taking into account the expiration.
+        assoc3 = _gen_assoc(issued=2, lifetime=100)
+        @store.store_association(server_url, assoc3)
+
+        _check_retrieve(server_url, nil, assoc3)
+        _check_retrieve(server_url, assoc.handle, assoc)
+        _check_retrieve(server_url, assoc2.handle, assoc2)
+        _check_retrieve(server_url, assoc3.handle, assoc3)
+
+        _check_remove(server_url, assoc2.handle, true)
+
+        _check_retrieve(server_url, nil, assoc3)
+        _check_retrieve(server_url, assoc.handle, assoc)
+        _check_retrieve(server_url, assoc2.handle, nil)
+        _check_retrieve(server_url, assoc3.handle, assoc3)
+
+        _check_remove(server_url, assoc2.handle, false)
+        _check_remove(server_url, assoc3.handle, true)
+
+        _check_retrieve(server_url, nil, assoc)
+        _check_retrieve(server_url, assoc.handle, assoc)
+        _check_retrieve(server_url, assoc2.handle, nil)
+        _check_retrieve(server_url, assoc3.handle, nil)
+
+        _check_remove(server_url, assoc2.handle, false)
+        _check_remove(server_url, assoc.handle, true)
+        _check_remove(server_url, assoc3.handle, false)
+
+        _check_retrieve(server_url, nil, nil)
+        _check_retrieve(server_url, assoc.handle, nil)
+        _check_retrieve(server_url, assoc2.handle, nil)
+        _check_retrieve(server_url, assoc3.handle, nil)
+
+        _check_remove(server_url, assoc2.handle, false)
+        _check_remove(server_url, assoc.handle, false)
+        _check_remove(server_url, assoc3.handle, false)
+
+        assocValid1 = _gen_assoc(-3600, 7200)
+        assocValid2 = _gen_assoc(-5)
+        assocExpired1 = _gen_assoc(-7200, 3600)
+        assocExpired2 = _gen_assoc(-7200, 3600)
+
+        @store.cleanup_associations
+        @store.store_association(server_url + '1', assocValid1)
+        @store.store_association(server_url + '1', assocExpired1)
+        @store.store_association(server_url + '2', assocExpired2)
+        @store.store_association(server_url + '3', assocValid2)
+
+        cleaned = @store.cleanup_associations()
+        assert_equal(2, cleaned, "cleaned up associations")
+      end
+
+      def _check_use_nonce(nonce, expected, server_url, msg='')
+        stamp, salt = Nonce::split_nonce(nonce)
+        actual = @store.use_nonce(server_url, stamp, salt)
+        assert_equal(expected, actual, msg)
+      end
+
+      def test_nonce
+        server_url = "http://www.myopenid.com/openid"
+        [server_url, ''].each{|url|
+          nonce1 = Nonce::mk_nonce
+
+          _check_use_nonce(nonce1, true, url, "#{url}: nonce allowed by default") 
+          _check_use_nonce(nonce1, false, url, "#{url}: nonce not allowed twice") 
+          _check_use_nonce(nonce1, false, url, "#{url}: nonce not allowed third time")
+          
+          # old nonces shouldn't pass
+          old_nonce = Nonce::mk_nonce(3600)
+          _check_use_nonce(old_nonce, false, url, "Old nonce #{old_nonce.inspect} passed")
+
+        }
+
+        now = Time.now.to_i
+        old_nonce1 = Nonce::mk_nonce(now - 20000)
+        old_nonce2 = Nonce::mk_nonce(now - 10000)
+        recent_nonce = Nonce::mk_nonce(now - 600)
+
+        orig_skew = Nonce.skew
+        Nonce.skew = 0
+        count = @store.cleanup_nonces
+        Nonce.skew = 1000000
+        ts, salt = Nonce::split_nonce(old_nonce1)
+        assert(@store.use_nonce(server_url, ts, salt), "oldnonce1")
+        ts, salt = Nonce::split_nonce(old_nonce2)
+        assert(@store.use_nonce(server_url, ts, salt), "oldnonce2")
+        ts, salt = Nonce::split_nonce(recent_nonce)
+        assert(@store.use_nonce(server_url, ts, salt), "recent_nonce")
+
+        
+        Nonce.skew = 1000
+        cleaned = @store.cleanup_nonces
+        assert_equal(2, cleaned, "Cleaned #{cleaned} nonces")
+
+        Nonce.skew = 100000
+        ts, salt = Nonce::split_nonce(old_nonce1)
+        assert(@store.use_nonce(server_url, ts, salt), "oldnonce1 after cleanup")
+        ts, salt = Nonce::split_nonce(old_nonce2)
+        assert(@store.use_nonce(server_url, ts, salt), "oldnonce2 after cleanup")
+        ts, salt = Nonce::split_nonce(recent_nonce)
+        assert(!@store.use_nonce(server_url, ts, salt), "recent_nonce after cleanup")
+
+        Nonce.skew = orig_skew
+
+      end
+    end
+    
+    class FileStoreTestCase < Test::Unit::TestCase
+      include StoreTestCase
+
+      def setup
+        raise "filestoretest directory exists" if File.exists?('filestoretest')
+        @store = Filesystem.new('filestoretest')
+      end
+
+      def teardown
+        Kernel.system('rm -r filestoretest')
+      end
+    end
+
+    class MemoryStoreTestCase < Test::Unit::TestCase
+      include StoreTestCase
+      
+      def setup
+        @store = Memory.new
+      end
+    end
+
+    class AbstractStoreTestCase < Test::Unit::TestCase
+      def test_abstract_class
+        # the abstract made concrete
+        abc = Interface.new()
+        server_url = "http://server.com/"
+        association = OpenID::Association.new("foo", "bar", Time.now, Time.now + 10, "dummy")
+        
+        assert_raise(NotImplementedError) { 
+          abc.store_association(server_url, association)
+        }
+
+        assert_raise(NotImplementedError) { 
+          abc.get_association(server_url)
+        }
+
+        assert_raise(NotImplementedError) { 
+          abc.remove_association(server_url, association.handle)
+        }
+
+        assert_raise(NotImplementedError) { 
+          abc.use_nonce(server_url, Time.now.to_i, "foo")
+        }
+
+        assert_raise(NotImplementedError) { 
+          abc.cleanup_nonces()
+        }
+
+        assert_raise(NotImplementedError) { 
+          abc.cleanup_associations()
+        }
+
+        assert_raise(NotImplementedError) { 
+          abc.cleanup()
+        }
+        
+      end
+
+    end
+  end
+end
+
diff --git a/vendor/gems/ruby-openid-2.1.4/test/test_trustroot.rb b/vendor/gems/ruby-openid-2.1.4/test/test_trustroot.rb
new file mode 100644 (file)
index 0000000..2616021
--- /dev/null
@@ -0,0 +1,113 @@
+require 'test/unit'
+require 'openid/trustroot'
+
+require "testutil"
+
+class TrustRootTest < Test::Unit::TestCase
+  include OpenID::TestDataMixin
+
+  def _test_sanity(case_, sanity, desc)
+    tr = OpenID::TrustRoot::TrustRoot.parse(case_)
+    if sanity == 'sane'
+      assert(! tr.nil?)
+      assert(tr.sane?, [case_, desc])
+      assert(OpenID::TrustRoot::TrustRoot.check_sanity(case_), [case_, desc])
+    elsif sanity == 'insane'
+      assert(!tr.sane?, [case_, desc])
+      assert(!OpenID::TrustRoot::TrustRoot.check_sanity(case_), [case_, desc])
+    else
+      assert(tr.nil?, case_)
+    end
+  end
+
+  def _test_match(trust_root, url, expected_match)
+    tr = OpenID::TrustRoot::TrustRoot.parse(trust_root)
+    actual_match = tr.validate_url(url)
+    if expected_match
+      assert(actual_match, [trust_root, url])
+      assert(OpenID::TrustRoot::TrustRoot.check_url(trust_root, url))
+    else
+      assert(!actual_match, [expected_match, actual_match, trust_root, url])
+      assert(!OpenID::TrustRoot::TrustRoot.check_url(trust_root, url))
+    end
+  end
+
+  def test_trustroots
+    data = read_data_file('trustroot.txt', false)
+
+    parts = data.split('=' * 40 + "\n").collect { |i| i.strip() }
+    assert(parts[0] == '')
+    _, ph, pdat, mh, mdat = parts
+
+    getTests(['bad', 'insane', 'sane'], ph, pdat).each { |tc|
+      sanity, desc, case_ = tc
+      _test_sanity(case_, sanity, desc)
+    }
+
+    getTests([true, false], mh, mdat).each { |tc|
+      match, desc, case_ = tc
+      trust_root, url = case_.split()
+      _test_match(trust_root, url, match)
+    }
+  end
+
+  def getTests(grps, head, dat)
+    tests = []
+    top = head.strip()
+    gdat = dat.split('-' * 40 + "\n").collect { |i| i.strip() }
+    assert(gdat[0] == '')
+    assert(gdat.length == (grps.length * 2 + 1), [gdat, grps])
+    i = 1
+    grps.each { |x|
+      n, desc = gdat[i].split(': ')
+      cases = gdat[i + 1].split("\n")
+      assert(cases.length == n.to_i, "Number of cases differs from header count")
+      cases.each { |case_|
+        tests += [[x, top + ' - ' + desc, case_]]
+      }
+      i += 2
+    }
+
+    return tests
+  end
+
+  def test_return_to_matches
+    data = [
+            [[], nil, false],
+            [[], "", false],
+            [[], "http://bogus/return_to", false],
+            [["http://bogus/"], nil, false],
+            [["://broken/"], nil, false],
+            [["://broken/"], "http://broken/", false],
+            [["http://*.broken/"], "http://foo.broken/", false],
+            [["http://x.broken/"], "http://foo.broken/", false],
+            [["http://first/", "http://second/path/"], "http://second/?query=x", false],
+
+            [["http://broken/"], "http://broken/", true],
+            [["http://first/", "http://second/"], "http://second/?query=x", true],
+           ]
+
+    data.each { |case_|
+      allowed_return_urls, return_to, expected_result = case_
+      actual_result = OpenID::TrustRoot::return_to_matches(allowed_return_urls,
+                                                           return_to)
+      assert(expected_result == actual_result)
+    }
+  end
+
+  def test_build_discovery_url
+    data = [
+            ["http://foo.com/path", "http://foo.com/path"],
+            ["http://foo.com/path?foo=bar", "http://foo.com/path?foo=bar"],
+            ["http://*.bogus.com/path", "http://www.bogus.com/path"],
+            ["http://*.bogus.com:122/path", "http://www.bogus.com:122/path"],
+           ]
+
+    data.each { |case_|
+      trust_root, expected_disco_url = case_
+      tr = OpenID::TrustRoot::TrustRoot.parse(trust_root)
+      actual_disco_url = tr.build_discovery_url()
+      assert(actual_disco_url == expected_disco_url, case_ + [actual_disco_url])
+    }
+  end
+end
diff --git a/vendor/gems/ruby-openid-2.1.4/test/test_urinorm.rb b/vendor/gems/ruby-openid-2.1.4/test/test_urinorm.rb
new file mode 100644 (file)
index 0000000..55c50e1
--- /dev/null
@@ -0,0 +1,35 @@
+require 'test/unit'
+
+require "openid/urinorm"
+require "testutil"
+
+class URINormTestCase < Test::Unit::TestCase
+  include OpenID::TestDataMixin
+
+  def test_normalize
+    lines = read_data_file('urinorm.txt')
+
+    while lines.length > 0
+
+      case_name = lines.shift.strip
+      actual = lines.shift.strip
+      expected = lines.shift.strip
+      _newline = lines.shift
+
+      if expected == 'fail'
+        begin
+          OpenID::URINorm.urinorm(actual)
+        rescue URI::InvalidURIError
+          assert true
+        else
+          raise 'Should have gotten URI error'
+        end
+      else
+        normalized = OpenID::URINorm.urinorm(actual)
+        assert_equal(expected, normalized, case_name)
+      end
+    end
+  end
+
+end
+
diff --git a/vendor/gems/ruby-openid-2.1.4/test/test_util.rb b/vendor/gems/ruby-openid-2.1.4/test/test_util.rb
new file mode 100644 (file)
index 0000000..ce1138a
--- /dev/null
@@ -0,0 +1,145 @@
+# coding: ASCII-8BIT
+require 'test/unit'
+
+require "openid/util"
+
+module OpenID
+  class UtilTestCase < Test::Unit::TestCase
+
+    def test_base64
+      cases = [
+               "",
+               "\000",
+               "\001",
+               "\000" * 100,
+               (0...256).collect{ |i| i.chr }.join('')
+              ]
+
+      cases.each do |c|
+        encoded = Util.to_base64(c)
+        decoded = Util.from_base64(encoded)
+        assert(c == decoded)
+      end
+
+    end
+
+    def test_base64_valid
+      [["foos", "~\212,"],
+       ["++++", "\373\357\276"],
+       ["/+==", "\377"],
+       ["", ""],
+       ["FOOSBALL", "\024\343\222\004\002\313"],
+       ["FoosBL==", "\026\212,\004"],
+       ["Foos\nBall", "\026\212,\005\251e"],
+       ["Foo\r\ns\nBall", "\026\212,\005\251e"]
+      ].each do | input, expected |
+        assert_equal(expected, Util.from_base64(input))
+      end
+    end
+
+    def test_base64_invalid
+      ['!',
+       'Foos!',
+       'Balls',
+       'B===',
+       'Foos Ball',
+       '=foo',
+      ].each do |invalid_input|
+        assert_raises(ArgumentError) do
+          Util.from_base64(invalid_input)
+        end
+      end
+    end
+
+    def test_append_args()
+      simple = 'http://www.example.com/'
+
+      cases = [
+               ['empty list',
+                [simple, []],
+                simple],
+
+               ['empty dict',
+                [simple, {}],
+                simple],
+
+               ['one list',
+                [simple, [['a', 'b']]],
+                simple + '?a=b'],
+
+               ['one dict',
+                [simple, {'a' => 'b'}],
+                simple + '?a=b'],
+
+               ['two list (same)',
+                [simple, [['a', 'b'], ['a', 'c']]],
+                simple + '?a=b&a=c'],
+
+               ['two list',
+                [simple, [['a', 'b'], ['b', 'c']]],
+                simple + '?a=b&b=c'],
+
+               ['two list (order)',
+                [simple, [['b', 'c'], ['a', 'b']]],
+                simple + '?b=c&a=b'],
+
+               ['two dict [order]',
+                [simple, {'b' => 'c', 'a' => 'b'}],
+                simple + '?a=b&b=c'],
+
+               ['args exist [empty]',
+                [simple + '?stuff=bother', []],
+                simple + '?stuff=bother'],
+
+               ['escape',
+                [simple, [['=', '=']]],
+                simple + '?%3D=%3D'],
+
+               ['escape [URL]',
+                [simple, [['this_url', simple]]],
+                simple + '?this_url=http%3A%2F%2Fwww.example.com%2F'],
+
+               ['use dots',
+                [simple, [['openid.stuff', 'bother']]],
+                simple + '?openid.stuff=bother'],
+
+               ['args exist',
+                [simple + '?stuff=bother', [['ack', 'ack']]],
+                simple + '?stuff=bother&ack=ack'],
+
+               ['args exist',
+                [simple + '?stuff=bother', [['ack', 'ack']]],
+                simple + '?stuff=bother&ack=ack'],
+
+               ['args exist [dict]',
+                [simple + '?stuff=bother', {'ack' => 'ack'}],
+                simple + '?stuff=bother&ack=ack'],
+
+               ['args exist [dict 2]',
+                [simple + '?stuff=bother', {'ack' => 'ack', 'zebra' => 'lion'}],
+                simple + '?stuff=bother&ack=ack&zebra=lion'],
+
+               ['three args [dict]',
+                [simple, {'stuff' => 'bother', 'ack' => 'ack', 'zebra' => 'lion'}],
+                simple + '?ack=ack&stuff=bother&zebra=lion'],
+
+               ['three args [list]',
+                [simple, [['stuff', 'bother'], ['ack', 'ack'], ['zebra', 'lion']]],
+                simple + '?stuff=bother&ack=ack&zebra=lion'],
+              ]
+
+      cases.each { |name, args, expected|
+        url, pairs = args
+        actual = Util.append_args(url, pairs)
+        msg = "[#{name}] Expected: #{expected}, actual: #{actual}"
+        assert_equal(expected, actual, msg)
+      }
+
+    end
+
+    def test_parse_query
+      assert_equal({'foo'=>'bar'}, Util.parse_query('foo=bar'))
+    end
+
+  end
+end
diff --git a/vendor/gems/ruby-openid-2.1.4/test/test_xrds.rb b/vendor/gems/ruby-openid-2.1.4/test/test_xrds.rb
new file mode 100644 (file)
index 0000000..ced7802
--- /dev/null
@@ -0,0 +1,169 @@
+
+require 'test/unit'
+require 'openid/yadis/xrds'
+
+require 'testutil'
+
+module OpenID
+  module Yadis
+
+    module XRDSTestMixin
+      include TestDataMixin
+
+      XRD_FILE = 'valid-populated-xrds.xml'
+      NOXRDS_FILE = 'not-xrds.xml'
+      NOXRD_FILE = 'no-xrd.xml'
+
+      XRDS_DATA_DIR = TEST_DATA_DIR.join('test_xrds')
+
+      def read_data_file(filename)
+        super(filename, false, XRDS_DATA_DIR)
+      end
+    end
+
+    class ParseXRDSTestCase < Test::Unit::TestCase
+      include XRDSTestMixin
+
+      # Check that parsing succeeds at all.
+      def test_parse
+        result = Yadis.parseXRDS(read_data_file(XRD_FILE))
+        assert_not_nil result
+      end
+
+      def test_parse_no_xrds_xml
+        xmldoc = read_data_file(NOXRDS_FILE)
+        assert_raise(Yadis::XRDSError) {
+          Yadis.parseXRDS(xmldoc)
+        }
+      end
+
+      def test_parse_no_xrds_empty
+        assert_raise(Yadis::XRDSError) {
+          Yadis.parseXRDS('')
+        }
+      end
+
+      def test_is_xrds
+        isnt = REXML::Document.new(read_data_file(NOXRDS_FILE))
+        should_be = Yadis.parseXRDS(read_data_file(XRD_FILE))
+        assert_equal false, Yadis::is_xrds?(isnt)
+        assert Yadis::is_xrds?(should_be)
+      end
+    end
+
+    class GetYadisXRDTestCase < Test::Unit::TestCase
+      include XRDSTestMixin
+
+      # XXX: Test to make sure this really gets the _right_ XRD.
+      def test_get_xrd
+        doc = Yadis.parseXRDS(read_data_file(XRD_FILE))
+        result = Yadis::get_yadis_xrd(doc)
+        assert_not_nil result
+        assert_equal 'XRD', result.name
+        assert_equal Yadis::XRD_NS_2_0, result.namespace
+      end
+
+      def test_no_xrd
+        xmldoc = read_data_file(NOXRD_FILE)
+        doc = Yadis.parseXRDS(xmldoc)
+        assert_raise(Yadis::XRDSError) {
+          Yadis.get_yadis_xrd(doc)
+        }
+      end
+    end
+
+    class EachServiceTestCase < Test::Unit::TestCase
+      include XRDSTestMixin
+
+      def test_get_xrd
+        doc = Yadis.parseXRDS(read_data_file(XRD_FILE))
+        count = 0
+        result = Yadis::each_service(doc) { |e|
+          assert_equal 'Service', e.name
+          count += 1
+        }
+        assert_not_nil result
+        assert_equal 5, count
+      end
+
+      def test_no_xrd
+        xmldoc = read_data_file(NOXRD_FILE)
+        doc = Yadis.parseXRDS(xmldoc)
+        assert_raise(Yadis::XRDSError) {
+          Yadis.each_service(doc)
+        }
+      end
+
+      def test_equal_j3h
+        doc = Yadis.parseXRDS(read_data_file('=j3h.2007.11.14.xrds'))
+        count = 0
+        result = Yadis::each_service(doc) { |e|
+          assert_equal 'Service', e.name
+          count += 1
+        }
+        assert_not_nil result
+        assert_equal 2, count
+      end
+    end
+
+    # XXX: test prioSort!
+
+    class ExpandServiceTestCase < Test::Unit::TestCase
+      @@service_xml = <<END
+<Service>
+<Type>urn://foo</Type>
+<Type>urn://bar</Type>
+<URI priority='2'>http://2.invalid/</URI>
+<URI>http://0.invalid/</URI>
+<URI priority='1'>http://1.invalid/</URI>
+</Service>
+END
+
+      # XXX - not sorted!
+      def test_expand_service
+        service_element = REXML::Document.new(@@service_xml).root
+        result = Yadis::expand_service(service_element)
+        assert_equal 3, result.length
+        types, uri, result_element = result[0]
+        assert_same service_element, result_element
+        assert_equal 'http://0.invalid/', uri
+        assert_equal ['urn://foo', 'urn://bar'], types
+        types, uri, result_element = result[1]
+        assert_equal 'http://1.invalid/', uri
+        types, uri, result_element = result[2]
+        assert_equal 'http://2.invalid/', uri
+      end
+    end
+
+    class PrioSortTestCase < Test::Unit::TestCase
+      def new_uri(priority)
+        e = REXML::Element.new("URI")
+        e.add_attribute("priority", priority.to_s) unless e.nil?
+        return e
+      end
+
+      def test_sorting
+        l = [
+             e7 = new_uri(7),
+             e1 = new_uri(1),
+             e0 = new_uri(nil),
+             e2 = new_uri(2),
+            ]
+        sorted = Yadis::prio_sort(l)
+        assert_same e0, sorted[0]
+        assert_same e1, sorted[1]
+        assert_same e2, sorted[2]
+        assert_same e7, sorted[3]
+      end
+    end
+
+    class GetCanonicalIDTestCase < Test::Unit::TestCase
+      include XRDSTestMixin
+
+      def test_multisegment_xri
+        xmldoc = Yadis.parseXRDS(read_data_file('subsegments.xrds'))
+        result = Yadis.get_canonical_id('xri://=nishitani*masaki', xmldoc)
+      end
+    end
+  end
+end
diff --git a/vendor/gems/ruby-openid-2.1.4/test/test_xri.rb b/vendor/gems/ruby-openid-2.1.4/test/test_xri.rb
new file mode 100644 (file)
index 0000000..ba20e9c
--- /dev/null
@@ -0,0 +1,48 @@
+require 'test/unit'
+require 'openid/yadis/xri'
+
+module OpenID
+
+  module Yadis
+
+    class XriDiscoveryTestCase < Test::Unit::TestCase
+
+      def test_isXRI?
+        assert_equal(:xri, XRI.identifier_scheme('=john.smith'))
+        assert_equal(:xri, XRI.identifier_scheme('@smiths/john'))
+        assert_equal(:xri, XRI.identifier_scheme('xri://=john'))
+        assert_equal(:xri, XRI.identifier_scheme('@ootao*test1'))
+        assert_equal(:uri, XRI.identifier_scheme('smoker.myopenid.com'))
+        assert_equal(:uri, XRI.identifier_scheme('http://smoker.myopenid.com'))
+        assert_equal(:uri, XRI.identifier_scheme('https://smoker.myopenid.com'))
+      end
+    end
+
+    class XriEscapingTestCase < Test::Unit::TestCase
+      def test_escaping_percents
+        assert_equal('@example/abc%252Fd/ef', 
+                     XRI.escape_for_iri('@example/abc%2Fd/ef'))
+      end
+
+      def test_escaping_xref
+        # no escapes
+        assert_equal('@example/foo/(@bar)',
+                     XRI.escape_for_iri('@example/foo/(@bar)'))
+        # escape slashes
+        assert_equal('@example/foo/(@bar%2Fbaz)',
+                     XRI.escape_for_iri('@example/foo/(@bar/baz)'))
+        # escape query ? and fragment #
+        assert_equal('@example/foo/(@baz%3Fp=q%23r)?i=j#k',
+                     XRI.escape_for_iri('@example/foo/(@baz?p=q#r)?i=j#k'))
+      end
+    end
+
+    class XriTransformationTestCase < Test::Unit::TestCase
+      def test_to_iri_normal
+        assert_equal('xri://@example', XRI.to_iri_normal('@example'))
+      end
+      # iri_to_url:
+      #   various ucschar to hex
+    end
+  end
+end
diff --git a/vendor/gems/ruby-openid-2.1.4/test/test_xrires.rb b/vendor/gems/ruby-openid-2.1.4/test/test_xrires.rb
new file mode 100644 (file)
index 0000000..3959361
--- /dev/null
@@ -0,0 +1,63 @@
+
+require 'test/unit'
+require 'openid/yadis/xrires'
+
+module OpenID
+  module Yadis
+
+    class XRDSFetcher
+      def initialize(results)
+        @results = results
+      end
+
+      def fetch(url, body=nil, headers=nil, redirect_limit=nil)
+        if !@results.empty?
+          return @results.shift
+        end
+
+        nil
+      end
+    end
+
+    class ProxyQueryTestCase < Test::Unit::TestCase
+      def setup
+        @proxy_url = 'http://xri.example.com/'
+        @proxy = XRI::ProxyResolver.new(@proxy_url)
+        @servicetype = 'xri://+i-service*(+forwarding)*($v*1.0)'
+        @servicetype_enc = 'xri%3A%2F%2F%2Bi-service%2A%28%2Bforwarding%29%2A%28%24v%2A1.0%29'
+      end
+
+      def test_proxy_url
+        st = @servicetype
+        ste = @servicetype_enc
+        args_esc = "_xrd_r=application%2Fxrds%2Bxml&_xrd_t=" + ste
+        pqu = @proxy.method('query_url')
+        h = @proxy_url
+
+        assert_equal(h + '=foo?' + args_esc, pqu.call('=foo', st))
+        assert_equal(h + '=foo/bar?baz&' + args_esc,
+                     pqu.call('=foo/bar?baz', st))
+        assert_equal(h + '=foo/bar?baz=quux&' + args_esc,
+                     pqu.call('=foo/bar?baz=quux', st))
+        assert_equal(h + '=foo/bar?mi=fa&so=la&' + args_esc,
+                     pqu.call('=foo/bar?mi=fa&so=la', st))
+
+        # With no service endpoint selection.
+        args_esc = "_xrd_r=application%2Fxrds%2Bxml%3Bsep%3Dfalse"
+        assert_equal(h + '=foo?' + args_esc, pqu.call('=foo', nil))
+      end
+
+      def test_proxy_url_qmarks
+        st = @servicetype
+        ste = @servicetype_enc
+        args_esc = "_xrd_r=application%2Fxrds%2Bxml&_xrd_t=" + ste
+        pqu = @proxy.method('query_url')
+        h = @proxy_url
+
+        assert_equal(h + '=foo/bar??' + args_esc, pqu.call('=foo/bar?', st))
+        assert_equal(h + '=foo/bar????' + args_esc,
+                     pqu.call('=foo/bar???', st))
+      end
+    end
+  end
+end
diff --git a/vendor/gems/ruby-openid-2.1.4/test/test_yadis_discovery.rb b/vendor/gems/ruby-openid-2.1.4/test/test_yadis_discovery.rb
new file mode 100644 (file)
index 0000000..b0fdd17
--- /dev/null
@@ -0,0 +1,220 @@
+
+require 'test/unit'
+require 'uri'
+require 'testutil'
+
+require 'openid/yadis/discovery'
+require 'openid/fetchers'
+require 'openid/util'
+require 'discoverdata'
+
+module OpenID
+
+  module YadisDiscovery
+    include FetcherMixin
+    include DiscoverData
+
+    STATUS_HEADER_RE = /Status: (\d+) .*?$/m
+
+    four04_pat = "\nContent-Type: text/plain\n\nNo such file %s"
+
+    def self.mkResponse(data)
+      status_mo = data.scan(STATUS_HEADER_RE)
+      headers_str, body = data.split("\n\n", 2)
+      headers = {}
+      headers_str.split("\n", -1).each { |line|
+        k, v = line.split(':', 2)
+        k = k.strip().downcase
+        v = v.strip()
+        headers[k] = v
+      }
+      status = status_mo[0][0].to_i
+      return HTTPResponse._from_raw_data(status, body,
+                                         headers)
+    end
+
+    class TestFetcher
+      include DiscoverData
+
+      def initialize(base_url)
+        @base_url = base_url
+      end
+
+      def fetch(url, headers, body, redirect_limit=nil)
+        current_url = url
+        while true
+          parsed = URI::parse(current_url)
+          # parsed[2][1:]
+          path = parsed.path[1..-1]
+          begin
+            data = generateSample(path, @base_url)
+          rescue ArgumentError
+            return HTTPResponse._from_raw_data(404, '', {},
+                                               current_url)
+          end
+
+          response = YadisDiscovery.mkResponse(data)
+          if ["301", "302", "303", "307"].member?(response.code)
+            current_url = response['location']
+          else
+            response.final_url = current_url
+            return response
+          end
+        end
+      end
+    end
+
+    class MockFetcher
+      def initialize
+        @count = 0
+      end
+
+      def fetch(uri, headers=nil, body=nil, redirect_limit=nil)
+        @count += 1
+        if @count == 1
+          headers = {
+            'X-XRDS-Location'.downcase => 'http://unittest/404',
+          }
+          return HTTPResponse._from_raw_data(200, '', headers, uri)
+        else
+          return HTTPResponse._from_raw_data(404, '', {}, uri)
+        end
+      end
+    end
+
+    class TestSecondGet < Test::Unit::TestCase
+      include FetcherMixin
+
+      def test_404
+        uri = "http://something.unittest/"
+        assert_raise(DiscoveryFailure) {
+          with_fetcher(MockFetcher.new) { Yadis.discover(uri) }
+        }
+      end
+    end
+
+    class DiscoveryTestCase
+      include DiscoverData
+      include FetcherMixin
+
+      def initialize(testcase, input_name, id_name, result_name, success)
+        @base_url = 'http://invalid.unittest/'
+        @testcase = testcase
+        @input_name = input_name
+        @id_name = id_name
+        @result_name = result_name
+        @success = success
+      end
+
+      def setup
+        @input_url, @expected = generateResult(@base_url,
+                                               @input_name,
+                                               @id_name,
+                                               @result_name,
+                                               @success)
+      end
+
+      def do_discovery
+        with_fetcher(TestFetcher.new(@base_url)) do
+          Yadis.discover(@input_url)
+        end
+      end
+
+      def runCustomTest
+        setup
+
+        if @expected.respond_to?("ancestors") and @expected.ancestors.member?(DiscoveryFailure)
+          @testcase.assert_raise(DiscoveryFailure) {
+            do_discovery
+          }
+        else
+          result = do_discovery
+          @testcase.assert_equal(@input_url, result.request_uri)
+
+          msg = sprintf("Identity URL mismatch: actual = %s, expected = %s",
+                        result.normalized_uri, @expected.normalized_uri)
+          @testcase.assert_equal(@expected.normalized_uri, result.normalized_uri, msg)
+
+          msg = sprintf("Content mismatch: actual = %s, expected = %s",
+                        result.response_text, @expected.response_text)
+          @testcase.assert_equal(@expected.response_text, result.response_text, msg)
+
+          expected_keys = @expected.instance_variables
+          expected_keys.sort!
+
+          actual_keys = result.instance_variables
+          actual_keys.sort!
+
+          @testcase.assert_equal(actual_keys, expected_keys)
+
+          @expected.instance_variables.each { |k|
+            exp_v = @expected.instance_variable_get(k)
+            act_v = result.instance_variable_get(k)
+            @testcase.assert_equal(act_v, exp_v, [k, exp_v, act_v])
+          }
+        end
+      end
+    end
+
+    class NoContentTypeFetcher
+      def fetch(url, body=nil, headers=nil, redirect_limit=nil)
+        return OpenID::HTTPResponse._from_raw_data(200, "", {}, nil)
+      end
+    end
+
+    class BlankContentTypeFetcher
+      def fetch(url, body=nil, headers=nil, redirect_limit=nil)
+        return OpenID::HTTPResponse._from_raw_data(200, "", {"Content-Type" => ""}, nil)
+      end
+    end
+
+    class TestYadisDiscovery < Test::Unit::TestCase
+      include FetcherMixin
+
+      def test_yadis_discovery
+        DiscoverData::TESTLIST.each { |success, input_name, id_name, result_name|
+          test = DiscoveryTestCase.new(self, input_name, id_name, result_name, success)
+          test.runCustomTest
+        }
+      end
+
+      def test_is_xrds_yadis_location
+        result = Yadis::DiscoveryResult.new('http://request.uri/')
+        result.normalized_uri = "http://normalized/"
+        result.xrds_uri = "http://normalized/xrds"
+
+        assert(result.is_xrds)
+      end
+
+      def test_is_xrds_content_type
+        result = Yadis::DiscoveryResult.new('http://request.uri/')
+        result.normalized_uri = result.xrds_uri = "http://normalized/"
+        result.content_type = Yadis::YADIS_CONTENT_TYPE
+
+        assert(result.is_xrds)
+      end
+
+      def test_is_xrds_neither
+        result = Yadis::DiscoveryResult.new('http://request.uri/')
+        result.normalized_uri = result.xrds_uri = "http://normalized/"
+        result.content_type = "another/content-type"
+
+        assert(!result.is_xrds)
+      end
+
+      def test_no_content_type
+        with_fetcher(NoContentTypeFetcher.new) do
+          result = Yadis.discover("http://bogus")
+          assert_equal(nil, result.content_type)
+        end
+      end
+
+      def test_blank_content_type
+        with_fetcher(BlankContentTypeFetcher.new) do
+          result = Yadis.discover("http://bogus")
+          assert_equal("", result.content_type)
+        end
+      end
+    end
+  end
+end
diff --git a/vendor/gems/ruby-openid-2.1.4/test/testutil.rb b/vendor/gems/ruby-openid-2.1.4/test/testutil.rb
new file mode 100644 (file)
index 0000000..7676fb9
--- /dev/null
@@ -0,0 +1,127 @@
+require "pathname"
+
+module OpenID
+  module TestDataMixin
+    TESTS_DIR = Pathname.new(__FILE__).dirname
+    TEST_DATA_DIR = Pathname.new('data')
+
+    def read_data_file(filename, lines=true, data_dir=TEST_DATA_DIR)
+      fname = TESTS_DIR.join(data_dir, filename)
+
+      if lines
+        fname.readlines
+      else
+        fname.read
+      end
+    end
+  end
+
+  module FetcherMixin
+    def with_fetcher(fetcher)
+      original_fetcher = OpenID.fetcher
+      begin
+        OpenID.fetcher = fetcher
+        return yield
+      ensure
+        OpenID.fetcher = original_fetcher
+      end
+    end
+  end
+
+  module Const
+    def const(symbol, value)
+      (class << self;self;end).instance_eval do
+        define_method(symbol) { value }
+      end
+    end
+  end
+
+  class MockResponse
+    attr_reader :code, :body
+
+    def initialize(code, body)
+      @code = code.to_s
+      @body = body
+    end
+  end
+
+  module ProtocolErrorMixin
+    def assert_protocol_error(str_prefix)
+      begin
+        result = yield
+      rescue ProtocolError => why
+        message = "Expected prefix #{str_prefix.inspect}, got "\
+                  "#{why.message.inspect}"
+        assert(why.message.starts_with?(str_prefix), message)
+      else
+        fail("Expected ProtocolError. Got #{result.inspect}")
+      end
+    end
+  end
+
+  module OverrideMethodMixin
+    def with_method_overridden(method_name, proc)
+      original = method(method_name)
+      begin
+        # TODO: find a combination of undef calls which prevent the warning
+        verbose, $VERBOSE = $VERBOSE, false
+        define_method(method_name, proc)
+        module_function(method_name)
+        $VERBOSE = verbose
+        yield
+      ensure
+        if original.respond_to? :owner
+          original.owner.send(:undef_method, method_name)
+          original.owner.send :define_method, method_name, original
+        else
+          define_method(method_name, original)
+          module_function(method_name)
+        end
+      end
+    end
+  end
+
+  # To use:
+  # > x = Object.new
+  # > x.extend(InstanceDefExtension)
+  # > x.instance_def(:monkeys) do
+  # >   "bananas"
+  # > end
+  # > x.monkeys
+  #
+  module InstanceDefExtension
+    def instance_def(method_name, &proc)
+      (class << self;self;end).instance_eval do
+        # TODO: find a combination of undef calls which prevent the warning
+        verbose, $VERBOSE = $VERBOSE, false
+        define_method(method_name, proc)
+        $VERBOSE = verbose
+      end
+    end
+  end
+
+  GOODSIG = '[A Good Signature]'
+
+  class GoodAssoc
+    attr_accessor :handle, :expires_in
+
+    def initialize(handle='-blah-')
+      @handle = handle
+      @expires_in = 3600
+    end
+
+    def check_message_signature(msg)
+      msg.get_arg(OPENID_NS, 'sig') == GOODSIG
+    end
+  end
+
+  class HTTPResponse
+    def self._from_raw_data(status, body="", headers={}, final_url=nil)
+      resp = Net::HTTPResponse.new('1.1', status.to_s, 'NONE')
+      me = self._from_net_response(resp, final_url)
+      me.initialize_http_header headers
+      me.body = body
+      return me
+    end
+  end
+end
diff --git a/vendor/gems/ruby-openid-2.1.4/test/util.rb b/vendor/gems/ruby-openid-2.1.4/test/util.rb
new file mode 100644 (file)
index 0000000..93bd096
--- /dev/null
@@ -0,0 +1,53 @@
+# Utilities that are only used in the testing code
+require 'stringio'
+
+module OpenID
+  module TestUtil
+    def assert_log_matches(*regexes)
+      begin
+        old_logger = Util.logger
+        log_output = StringIO.new
+        Util.logger = Logger.new(log_output)
+        result = yield
+      ensure
+        Util.logger = old_logger
+      end
+      log_output.rewind
+      log_lines = log_output.readlines
+      assert_equal(regexes.length, log_lines.length,
+                   [regexes, log_lines].inspect)
+      log_lines.zip(regexes) do |line, regex|
+        assert_match(regex, line)
+      end
+      result
+    end
+
+    def assert_log_line_count(num_lines)
+      begin
+        old_logger = Util.logger
+        log_output = StringIO.new
+        Util.logger = Logger.new(log_output)
+        result = yield
+      ensure
+        Util.logger = old_logger
+      end
+      log_output.rewind
+      log_lines = log_output.readlines
+      assert_equal(num_lines, log_lines.length)
+      result
+    end
+
+    def silence_logging
+      begin
+        old_logger = Util.logger
+        log_output = StringIO.new
+        Util.logger = Logger.new(log_output)
+        result = yield
+      ensure
+        Util.logger = old_logger
+      end
+      result
+    end
+  end
+end
+