diff options
author | Eric Davis <edavis@littlestreamsoftware.com> | 2009-02-11 19:06:37 +0000 |
---|---|---|
committer | Eric Davis <edavis@littlestreamsoftware.com> | 2009-02-11 19:06:37 +0000 |
commit | f70be197e0aefd2d352faf3749d0db0d2e7b5c14 (patch) | |
tree | 0cf6685c60010035322a5b69587bf9078a21403d /vendor/gems | |
parent | 70efee1bc5b87377c093163a58e0b9bb03a59778 (diff) | |
download | redmine-f70be197e0aefd2d352faf3749d0db0d2e7b5c14.tar.gz redmine-f70be197e0aefd2d352faf3749d0db0d2e7b5c14.zip |
Unpacked OpenID gem. #699
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2437 e93f8b46-1217-0410-a6f0-8f06a7374b81
Diffstat (limited to 'vendor/gems')
190 files changed, 30000 insertions, 0 deletions
diff --git a/vendor/gems/ruby-openid-2.1.4/.specification b/vendor/gems/ruby-openid-2.1.4/.specification new file mode 100644 index 000000000..489cda032 --- /dev/null +++ b/vendor/gems/ruby-openid-2.1.4/.specification @@ -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 index 000000000..123db5898 --- /dev/null +++ b/vendor/gems/ruby-openid-2.1.4/CHANGELOG @@ -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 index 000000000..89f1b9bd4 --- /dev/null +++ b/vendor/gems/ruby-openid-2.1.4/INSTALL @@ -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 index 000000000..c1c57734d --- /dev/null +++ b/vendor/gems/ruby-openid-2.1.4/LICENSE @@ -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 index 000000000..62df93be6 --- /dev/null +++ b/vendor/gems/ruby-openid-2.1.4/NOTICE @@ -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 index 000000000..c6e833b43 --- /dev/null +++ b/vendor/gems/ruby-openid-2.1.4/README @@ -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 index 000000000..b80e82321 --- /dev/null +++ b/vendor/gems/ruby-openid-2.1.4/UPGRADE @@ -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 index 000000000..50abe0449 --- /dev/null +++ b/vendor/gems/ruby-openid-2.1.4/admin/runtests.rb @@ -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 index 000000000..71aa30d7e --- /dev/null +++ b/vendor/gems/ruby-openid-2.1.4/examples/README @@ -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 index 000000000..117872980 --- /dev/null +++ b/vendor/gems/ruby-openid-2.1.4/examples/active_record_openid_store/README @@ -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 index 000000000..996254531 --- /dev/null +++ b/vendor/gems/ruby-openid-2.1.4/examples/active_record_openid_store/XXX_add_open_id_store_to_db.rb @@ -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 index 000000000..273d285bd --- /dev/null +++ b/vendor/gems/ruby-openid-2.1.4/examples/active_record_openid_store/XXX_upgrade_open_id_store.rb @@ -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 index 000000000..b625179e2 --- /dev/null +++ b/vendor/gems/ruby-openid-2.1.4/examples/active_record_openid_store/init.rb @@ -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 index 000000000..09eda8b57 --- /dev/null +++ b/vendor/gems/ruby-openid-2.1.4/examples/active_record_openid_store/lib/association.rb @@ -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 index 000000000..fcf515302 --- /dev/null +++ b/vendor/gems/ruby-openid-2.1.4/examples/active_record_openid_store/lib/nonce.rb @@ -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 index 000000000..030e4c256 --- /dev/null +++ b/vendor/gems/ruby-openid-2.1.4/examples/active_record_openid_store/lib/open_id_setting.rb @@ -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 index 000000000..276569c5a --- /dev/null +++ b/vendor/gems/ruby-openid-2.1.4/examples/active_record_openid_store/lib/openid_ar_store.rb @@ -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 index 000000000..8e1986c6e --- /dev/null +++ b/vendor/gems/ruby-openid-2.1.4/examples/active_record_openid_store/test/store_test.rb @@ -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 index 000000000..ab985a496 --- /dev/null +++ b/vendor/gems/ruby-openid-2.1.4/examples/discover @@ -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 index 000000000..cd9d0ffe5 --- /dev/null +++ b/vendor/gems/ruby-openid-2.1.4/examples/rails_openid/README @@ -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 index 000000000..cffd19f0c --- /dev/null +++ b/vendor/gems/ruby-openid-2.1.4/examples/rails_openid/Rakefile @@ -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 index 000000000..537de40d7 --- /dev/null +++ b/vendor/gems/ruby-openid-2.1.4/examples/rails_openid/app/controllers/application.rb @@ -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 index 000000000..37dd3bbd4 --- /dev/null +++ b/vendor/gems/ruby-openid-2.1.4/examples/rails_openid/app/controllers/consumer_controller.rb @@ -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 index 000000000..ff7c257b5 --- /dev/null +++ b/vendor/gems/ruby-openid-2.1.4/examples/rails_openid/app/controllers/login_controller.rb @@ -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 index 000000000..af0b1a7c6 --- /dev/null +++ b/vendor/gems/ruby-openid-2.1.4/examples/rails_openid/app/controllers/server_controller.rb @@ -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 index 000000000..22a7940eb --- /dev/null +++ b/vendor/gems/ruby-openid-2.1.4/examples/rails_openid/app/helpers/application_helper.rb @@ -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 index 000000000..a0418e32b --- /dev/null +++ b/vendor/gems/ruby-openid-2.1.4/examples/rails_openid/app/helpers/login_helper.rb @@ -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 index 000000000..409b21048 --- /dev/null +++ b/vendor/gems/ruby-openid-2.1.4/examples/rails_openid/app/helpers/server_helper.rb @@ -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 index 000000000..0cecbe8e5 --- /dev/null +++ b/vendor/gems/ruby-openid-2.1.4/examples/rails_openid/app/views/consumer/index.rhtml @@ -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 index 000000000..3dd5d7862 --- /dev/null +++ b/vendor/gems/ruby-openid-2.1.4/examples/rails_openid/app/views/layouts/server.rhtml @@ -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 index 000000000..9990a5758 --- /dev/null +++ b/vendor/gems/ruby-openid-2.1.4/examples/rails_openid/app/views/login/index.rhtml @@ -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 index 000000000..5322b48a4 --- /dev/null +++ b/vendor/gems/ruby-openid-2.1.4/examples/rails_openid/app/views/server/decide.rhtml @@ -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 index 000000000..9fcd50fe3 --- /dev/null +++ b/vendor/gems/ruby-openid-2.1.4/examples/rails_openid/config/boot.rb @@ -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 index 000000000..0a78bf838 --- /dev/null +++ b/vendor/gems/ruby-openid-2.1.4/examples/rails_openid/config/environment.rb @@ -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 index 000000000..04b779200 --- /dev/null +++ b/vendor/gems/ruby-openid-2.1.4/examples/rails_openid/config/environments/development.rb @@ -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 index 000000000..c9a4396c4 --- /dev/null +++ b/vendor/gems/ruby-openid-2.1.4/examples/rails_openid/config/environments/production.rb @@ -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 index 000000000..6a4cddbd2 --- /dev/null +++ b/vendor/gems/ruby-openid-2.1.4/examples/rails_openid/config/environments/test.rb @@ -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 index 000000000..d5ceed206 --- /dev/null +++ b/vendor/gems/ruby-openid-2.1.4/examples/rails_openid/config/routes.rb @@ -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 index 000000000..ac6c14912 --- /dev/null +++ b/vendor/gems/ruby-openid-2.1.4/examples/rails_openid/doc/README_FOR_APP @@ -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 index 000000000..0e1845619 --- /dev/null +++ b/vendor/gems/ruby-openid-2.1.4/examples/rails_openid/public/404.html @@ -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 index 000000000..a1001a008 --- /dev/null +++ b/vendor/gems/ruby-openid-2.1.4/examples/rails_openid/public/500.html @@ -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 index 000000000..dfe5dc30e --- /dev/null +++ b/vendor/gems/ruby-openid-2.1.4/examples/rails_openid/public/dispatch.cgi @@ -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 index 000000000..d02c35b8b --- /dev/null +++ b/vendor/gems/ruby-openid-2.1.4/examples/rails_openid/public/dispatch.fcgi @@ -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 index 000000000..dfe5dc30e --- /dev/null +++ b/vendor/gems/ruby-openid-2.1.4/examples/rails_openid/public/dispatch.rb @@ -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 index 000000000..e69de29bb --- /dev/null +++ b/vendor/gems/ruby-openid-2.1.4/examples/rails_openid/public/favicon.ico 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 Binary files differnew file mode 100644 index 000000000..cde836c89 --- /dev/null +++ b/vendor/gems/ruby-openid-2.1.4/examples/rails_openid/public/images/openid_login_bg.gif 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 index 000000000..9742b6918 --- /dev/null +++ b/vendor/gems/ruby-openid-2.1.4/examples/rails_openid/public/javascripts/controls.js @@ -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 index 000000000..92d1f7316 --- /dev/null +++ b/vendor/gems/ruby-openid-2.1.4/examples/rails_openid/public/javascripts/dragdrop.js @@ -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 index 000000000..414398ce4 --- /dev/null +++ b/vendor/gems/ruby-openid-2.1.4/examples/rails_openid/public/javascripts/effects.js @@ -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 index 000000000..e9ccd3c88 --- /dev/null +++ b/vendor/gems/ruby-openid-2.1.4/examples/rails_openid/public/javascripts/prototype.js @@ -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 index 000000000..4ab9e89fe --- /dev/null +++ b/vendor/gems/ruby-openid-2.1.4/examples/rails_openid/public/robots.txt @@ -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 index 000000000..7b07d46a3 --- /dev/null +++ b/vendor/gems/ruby-openid-2.1.4/examples/rails_openid/script/about @@ -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 index 000000000..64af76edd --- /dev/null +++ b/vendor/gems/ruby-openid-2.1.4/examples/rails_openid/script/breakpointer @@ -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 index 000000000..42f28f7d6 --- /dev/null +++ b/vendor/gems/ruby-openid-2.1.4/examples/rails_openid/script/console @@ -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 index 000000000..fa0e6fcd0 --- /dev/null +++ b/vendor/gems/ruby-openid-2.1.4/examples/rails_openid/script/destroy @@ -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 index 000000000..ef976e09f --- /dev/null +++ b/vendor/gems/ruby-openid-2.1.4/examples/rails_openid/script/generate @@ -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 index 000000000..c842d35d3 --- /dev/null +++ b/vendor/gems/ruby-openid-2.1.4/examples/rails_openid/script/performance/benchmarker @@ -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 index 000000000..d855ac8b1 --- /dev/null +++ b/vendor/gems/ruby-openid-2.1.4/examples/rails_openid/script/performance/profiler @@ -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 index 000000000..26ca64c06 --- /dev/null +++ b/vendor/gems/ruby-openid-2.1.4/examples/rails_openid/script/plugin @@ -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 index 000000000..c77f04535 --- /dev/null +++ b/vendor/gems/ruby-openid-2.1.4/examples/rails_openid/script/process/reaper @@ -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 index 000000000..7118f3983 --- /dev/null +++ b/vendor/gems/ruby-openid-2.1.4/examples/rails_openid/script/process/spawner @@ -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 index 000000000..6816b32ef --- /dev/null +++ b/vendor/gems/ruby-openid-2.1.4/examples/rails_openid/script/process/spinner @@ -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 index 000000000..ccc30f9d2 --- /dev/null +++ b/vendor/gems/ruby-openid-2.1.4/examples/rails_openid/script/runner @@ -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 index 000000000..dfabcb881 --- /dev/null +++ b/vendor/gems/ruby-openid-2.1.4/examples/rails_openid/script/server @@ -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 index 000000000..a21d43825 --- /dev/null +++ b/vendor/gems/ruby-openid-2.1.4/examples/rails_openid/test/functional/login_controller_test.rb @@ -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 index 000000000..7afd69e64 --- /dev/null +++ b/vendor/gems/ruby-openid-2.1.4/examples/rails_openid/test/functional/server_controller_test.rb @@ -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 index 000000000..a299c7f6d --- /dev/null +++ b/vendor/gems/ruby-openid-2.1.4/examples/rails_openid/test/test_helper.rb @@ -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 index 000000000..e8bfa42bc --- /dev/null +++ b/vendor/gems/ruby-openid-2.1.4/lib/hmac/hmac.rb @@ -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 index 000000000..d2f0088a3 --- /dev/null +++ b/vendor/gems/ruby-openid-2.1.4/lib/hmac/sha1.rb @@ -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 index 000000000..0412ba408 --- /dev/null +++ b/vendor/gems/ruby-openid-2.1.4/lib/hmac/sha2.rb @@ -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 index 000000000..0a5643451 --- /dev/null +++ b/vendor/gems/ruby-openid-2.1.4/lib/openid.rb @@ -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 index 000000000..fd2cd5991 --- /dev/null +++ b/vendor/gems/ruby-openid-2.1.4/lib/openid/association.rb @@ -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 index 000000000..afe025a00 --- /dev/null +++ b/vendor/gems/ruby-openid-2.1.4/lib/openid/consumer.rb @@ -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 index 000000000..51c0f3d25 --- /dev/null +++ b/vendor/gems/ruby-openid-2.1.4/lib/openid/consumer/associationmanager.rb @@ -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 index 000000000..eb5d3979d --- /dev/null +++ b/vendor/gems/ruby-openid-2.1.4/lib/openid/consumer/checkid_request.rb @@ -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 index 000000000..01fe03656 --- /dev/null +++ b/vendor/gems/ruby-openid-2.1.4/lib/openid/consumer/discovery.rb @@ -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 index 000000000..8f838117d --- /dev/null +++ b/vendor/gems/ruby-openid-2.1.4/lib/openid/consumer/discovery_manager.rb @@ -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 index 000000000..579874caf --- /dev/null +++ b/vendor/gems/ruby-openid-2.1.4/lib/openid/consumer/html_parse.rb @@ -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('&','&').gsub('<','<').gsub('>','>').gsub('"','"') + 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 index 000000000..ab924e294 --- /dev/null +++ b/vendor/gems/ruby-openid-2.1.4/lib/openid/consumer/idres.rb @@ -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 index 000000000..91262398e --- /dev/null +++ b/vendor/gems/ruby-openid-2.1.4/lib/openid/consumer/responses.rb @@ -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 index 000000000..d8ffead9b --- /dev/null +++ b/vendor/gems/ruby-openid-2.1.4/lib/openid/cryptutil.rb @@ -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 index 000000000..cbe531147 --- /dev/null +++ b/vendor/gems/ruby-openid-2.1.4/lib/openid/dh.rb @@ -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 index 000000000..f0f02bb5c --- /dev/null +++ b/vendor/gems/ruby-openid-2.1.4/lib/openid/extension.rb @@ -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 index 000000000..55eda8e7c --- /dev/null +++ b/vendor/gems/ruby-openid-2.1.4/lib/openid/extensions/ax.rb @@ -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 index 000000000..0a7413c1b --- /dev/null +++ b/vendor/gems/ruby-openid-2.1.4/lib/openid/extensions/pape.rb @@ -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 index 000000000..8dc780eb0 --- /dev/null +++ b/vendor/gems/ruby-openid-2.1.4/lib/openid/extensions/sreg.rb @@ -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 index 000000000..0d9560abc --- /dev/null +++ b/vendor/gems/ruby-openid-2.1.4/lib/openid/extras.rb @@ -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 index 000000000..22c87ac33 --- /dev/null +++ b/vendor/gems/ruby-openid-2.1.4/lib/openid/fetchers.rb @@ -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 index 000000000..c534d203e --- /dev/null +++ b/vendor/gems/ruby-openid-2.1.4/lib/openid/kvform.rb @@ -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 index 000000000..1495afe74 --- /dev/null +++ b/vendor/gems/ruby-openid-2.1.4/lib/openid/kvpost.rb @@ -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 index 000000000..8700378ba --- /dev/null +++ b/vendor/gems/ruby-openid-2.1.4/lib/openid/message.rb @@ -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 index 000000000..2aad0e4a2 --- /dev/null +++ b/vendor/gems/ruby-openid-2.1.4/lib/openid/protocolerror.rb @@ -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 index 000000000..897b8bdb3 --- /dev/null +++ b/vendor/gems/ruby-openid-2.1.4/lib/openid/server.rb @@ -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 index 000000000..e2993eea4 --- /dev/null +++ b/vendor/gems/ruby-openid-2.1.4/lib/openid/store/filesystem.rb @@ -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 index 000000000..50819f6f7 --- /dev/null +++ b/vendor/gems/ruby-openid-2.1.4/lib/openid/store/interface.rb @@ -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 index 000000000..58455b958 --- /dev/null +++ b/vendor/gems/ruby-openid-2.1.4/lib/openid/store/memory.rb @@ -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 index 000000000..08a9e5298 --- /dev/null +++ b/vendor/gems/ruby-openid-2.1.4/lib/openid/store/nonce.rb @@ -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 index 000000000..16695f05c --- /dev/null +++ b/vendor/gems/ruby-openid-2.1.4/lib/openid/trustroot.rb @@ -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 index 000000000..f30893eaa --- /dev/null +++ b/vendor/gems/ruby-openid-2.1.4/lib/openid/urinorm.rb @@ -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 index 000000000..c5a6716b2 --- /dev/null +++ b/vendor/gems/ruby-openid-2.1.4/lib/openid/util.rb @@ -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 index 000000000..a1657482f --- /dev/null +++ b/vendor/gems/ruby-openid-2.1.4/lib/openid/yadis/accept.rb @@ -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 index 000000000..99b58b138 --- /dev/null +++ b/vendor/gems/ruby-openid-2.1.4/lib/openid/yadis/constants.rb @@ -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 index 000000000..55d6f09b1 --- /dev/null +++ b/vendor/gems/ruby-openid-2.1.4/lib/openid/yadis/discovery.rb @@ -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 index 000000000..90f350ea3 --- /dev/null +++ b/vendor/gems/ruby-openid-2.1.4/lib/openid/yadis/filters.rb @@ -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 index 000000000..b20810975 --- /dev/null +++ b/vendor/gems/ruby-openid-2.1.4/lib/openid/yadis/htmltokenizer.rb @@ -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 index 000000000..877c714bc --- /dev/null +++ b/vendor/gems/ruby-openid-2.1.4/lib/openid/yadis/parsehtml.rb @@ -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 index 000000000..e3b3e0f69 --- /dev/null +++ b/vendor/gems/ruby-openid-2.1.4/lib/openid/yadis/services.rb @@ -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 index 000000000..0ebf34ab4 --- /dev/null +++ b/vendor/gems/ruby-openid-2.1.4/lib/openid/yadis/xrds.rb @@ -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 index 000000000..89dd99afc --- /dev/null +++ b/vendor/gems/ruby-openid-2.1.4/lib/openid/yadis/xri.rb @@ -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 index 000000000..394391198 --- /dev/null +++ b/vendor/gems/ruby-openid-2.1.4/lib/openid/yadis/xrires.rb @@ -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 index 000000000..884ff662e --- /dev/null +++ b/vendor/gems/ruby-openid-2.1.4/test/data/accept.txt @@ -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 index 000000000..0fa523146 --- /dev/null +++ b/vendor/gems/ruby-openid-2.1.4/test/data/dh.txt @@ -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 index 000000000..101ba3bd5 --- /dev/null +++ b/vendor/gems/ruby-openid-2.1.4/test/data/example-xrds.xml @@ -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 index 000000000..2fa38cde9 --- /dev/null +++ b/vendor/gems/ruby-openid-2.1.4/test/data/linkparse.txt @@ -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="&"> + + +Name: entity not processed +Link: x= + +<html><head><link x=""> + + +Name: < +Link: x=< + +<html><head><link x="<"> + + +Name: > +Link: x=> + +<html><head><link x=">"> + + +Name: " +Link: x=" + +<html><head><link x="""> + + +Name: &" +Link: x=&" + +<html><head><link x="&""> + + +Name: mixed entity and non-entity +Link: x=&"…> + +<html><head><link x="&"…>"> + + +Name: mixed entity and non-entity (w/normal chars) +Link: x=x&"…>x + +<html><head><link x="x&"…>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 index 000000000..b12a24604 --- /dev/null +++ b/vendor/gems/ruby-openid-2.1.4/test/data/n2b64 @@ -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 index 000000000..7ec9b8788 --- /dev/null +++ b/vendor/gems/ruby-openid-2.1.4/test/data/test1-discover.txt @@ -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> + +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> + +xrds +Status: 200 OK +Content-Type: application/xrds+xml + +<XRDS Content> + +xrds_ctparam +Status: 200 OK +Content-Type: application/xrds+xml; charset=UTF8 + +<XRDS Content> + +xrds_ctcase +Status: 200 OK +Content-Type: appliCATION/XRDS+xml + +<XRDS Content> + +xrds_html +Status: 200 OK +Content-Type: text/html + +<XRDS Content> + +redir_equiv +Status: 302 Found +Content-Type: text/plain +Location: URL_BASE/equiv + +You are presently being redirected. + +redir_header +Status: 302 Found +Content-Type: text/plain +Location: URL_BASE/header + +You are presently being redirected. + +redir_xrds +Status: 302 Found +Content-Type: application/xrds+xml +Location: URL_BASE/xrds + +<XRDS Content> + +redir_xrds_html +Status: 302 Found +Content-Type: text/plain +Location: URL_BASE/xrds_html + +You are presently being redirected. + +redir_redir_equiv +Status: 302 Found +Content-Type: text/plain +Location: URL_BASE/redir_equiv + +You are presently being redirected. + +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> + +404_server_response +Status: 404 Not Found + +EEk! + +500_server_response +Status: 500 Server error + +EEk! + +201_server_response +Status: 201 Created + +EEk! + +404_with_header +Status: 404 Not Found +YADIS_HEADER: URL_BASE/xrds + +EEk! + +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 index 000000000..8ed59d33e --- /dev/null +++ b/vendor/gems/ruby-openid-2.1.4/test/data/test1-parsehtml.txt @@ -0,0 +1,152 @@ +found +<!-- minimal well-formed success case --> +<html><head><meta http-equiv="X-XRDS-Location" content="found"></head></html> + +found +<!-- minimal well-formed success case, xhtml closing, whitespace --> +<html><head><meta http-equiv="X-XRDS-Location" content="found" /></head></html> + +found +<!-- minimal well-formed success case, xhtml closing, no whitespace --> +<html><head><meta http-equiv="X-XRDS-Location" content="found"/></head></html> + +found +<!-- minimal success case --> +<html><head><meta http-equiv="X-XRDS-Location" content="found"> + +found +<!-- ignore bogus top-level tags --> +</porky><html><head><meta http-equiv="X-XRDS-Location" content="found"> + +found +<!-- Case folding for header name --> +<html><head><meta http-equiv="x-xrds-location" content="found"> + +found +<!-- missing <html> tag --> +<head><meta http-equiv="X-XRDS-Location" content="found"> + +found +<!-- javascript in head --> +<html><head><script type="text/javascript">document.write("<body>");</script><META http-equiv="X-XRDS-Location" content="found"> + +None +<!-- no close script tag in head --> +<html><head><script type="text/javascript">document.write("<body>");<META http-equiv="X-XRDS-Location" content="found"> + +found +<!-- case folding for tag names --> +<html><head><META http-equiv="X-XRDS-Location" content="found"> + +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"> + +& +<!-- standard entity --> +<head><meta http-equiv="X-XRDS-Location" content="&"> + +found +<!-- hex entity --> +<html> + <head> + <meta http-equiv="X-XRDS-Location" content="found"> + </head> +</html> + +found +<!-- decimal entity --> +<html> + <head> + <meta http-equiv="X-XRDS-Location" content="found"> + </head> +</html> + +/ +<!-- hex entity --> +<html> + <head> + <meta http-equiv="X-XRDS-Location" content="/"> + </head> +</html> + + +<!-- empty string --> +<html><head><meta http-equiv="X-XRDS-Location" content=""> + +EOF +<!-- No markup, except this comment --> + +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> + +EOF +<!-- No <html> or <head> --> +<meta http-equiv="X-XRDS-Location" content="found"> + +EOF +<!-- No <head> tag --> +<html><meta http-equiv="X-XRDS-Location" content="found"> + +None +<!-- No <html> or <head> and a <body> --> +<body><meta http-equiv="X-XRDS-Location" content="found"> + +None +<!-- <head> and <html> reversed --> +<head><html><meta http-equiv="X-XRDS-Location" content="found"> + +None +<!-- <meta> is inside of <body> --> +<html><head><body><meta http-equiv="X-XRDS-Location" content="found"> + +None +<!-- <meta> is inside comment --> +<html> + <head> + <!--<meta http-equiv="X-XRDS-Location" content="found">--> + </head> +</html> + +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> + +None +<!-- short head tag --> +<html><head/> +<meta http-equiv="X-XRDS-Location" content="found"> + +None +<!-- <body> comes first --> +<body><html><head> +<meta http-equiv="X-XRDS-Location" content="found"> + +None +<!-- </body> comes first --> +</body><html><head> +<meta http-equiv="X-XRDS-Location" content="found"> + +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 index 000000000..80dd3b45a --- /dev/null +++ b/vendor/gems/ruby-openid-2.1.4/test/data/test_discover/malformed_meta_tag.html @@ -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 index 000000000..1a57d44f2 --- /dev/null +++ b/vendor/gems/ruby-openid-2.1.4/test/data/test_discover/openid.html @@ -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 index 000000000..a74c042e7 --- /dev/null +++ b/vendor/gems/ruby-openid-2.1.4/test/data/test_discover/openid2.html @@ -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 index 000000000..8091ab94d --- /dev/null +++ b/vendor/gems/ruby-openid-2.1.4/test/data/test_discover/openid2_xrds.xml @@ -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 index 000000000..e6a0eb977 --- /dev/null +++ b/vendor/gems/ruby-openid-2.1.4/test/data/test_discover/openid2_xrds_no_local_id.xml @@ -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 index 000000000..5e581287b --- /dev/null +++ b/vendor/gems/ruby-openid-2.1.4/test/data/test_discover/openid_1_and_2.html @@ -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 index 000000000..6d85d57e3 --- /dev/null +++ b/vendor/gems/ruby-openid-2.1.4/test/data/test_discover/openid_1_and_2_xrds.xml @@ -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 index 000000000..db7282e2b --- /dev/null +++ b/vendor/gems/ruby-openid-2.1.4/test/data/test_discover/openid_1_and_2_xrds_bad_delegate.xml @@ -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 index 000000000..3befa6fc3 --- /dev/null +++ b/vendor/gems/ruby-openid-2.1.4/test/data/test_discover/openid_and_yadis.html @@ -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 index 000000000..f5180b3dc --- /dev/null +++ b/vendor/gems/ruby-openid-2.1.4/test/data/test_discover/openid_no_delegate.html @@ -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 index 000000000..f161a0b31 --- /dev/null +++ b/vendor/gems/ruby-openid-2.1.4/test/data/test_discover/yadis_0entries.xml @@ -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 index 000000000..68c2ce1fc --- /dev/null +++ b/vendor/gems/ruby-openid-2.1.4/test/data/test_discover/yadis_2_bad_local_id.xml @@ -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 index 000000000..372955b02 --- /dev/null +++ b/vendor/gems/ruby-openid-2.1.4/test/data/test_discover/yadis_2entries_delegate.xml @@ -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 index 000000000..9a07b3d40 --- /dev/null +++ b/vendor/gems/ruby-openid-2.1.4/test/data/test_discover/yadis_2entries_idp.xml @@ -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 index 000000000..2f3b9af37 --- /dev/null +++ b/vendor/gems/ruby-openid-2.1.4/test/data/test_discover/yadis_another_delegate.xml @@ -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 index 000000000..f570d0437 --- /dev/null +++ b/vendor/gems/ruby-openid-2.1.4/test/data/test_discover/yadis_idp.xml @@ -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 index 000000000..541060053 --- /dev/null +++ b/vendor/gems/ruby-openid-2.1.4/test/data/test_discover/yadis_idp_delegate.xml @@ -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 index 000000000..fbd673490 --- /dev/null +++ b/vendor/gems/ruby-openid-2.1.4/test/data/test_discover/yadis_no_delegate.xml @@ -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 index 000000000..f0246e22a --- /dev/null +++ b/vendor/gems/ruby-openid-2.1.4/test/data/test_xrds/=j3h.2007.11.14.xrds @@ -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 index 000000000..3739cf1c7 --- /dev/null +++ b/vendor/gems/ruby-openid-2.1.4/test/data/test_xrds/README @@ -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 index 000000000..f994b140e --- /dev/null +++ b/vendor/gems/ruby-openid-2.1.4/test/data/test_xrds/delegated-20060809-r1.xrds @@ -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 index 000000000..68c08dc44 --- /dev/null +++ b/vendor/gems/ruby-openid-2.1.4/test/data/test_xrds/delegated-20060809-r2.xrds @@ -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 index 000000000..073ee6889 --- /dev/null +++ b/vendor/gems/ruby-openid-2.1.4/test/data/test_xrds/delegated-20060809.xrds @@ -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 index 000000000..ca66f7359 --- /dev/null +++ b/vendor/gems/ruby-openid-2.1.4/test/data/test_xrds/no-xrd.xml @@ -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 index 000000000..7f5bfd511 --- /dev/null +++ b/vendor/gems/ruby-openid-2.1.4/test/data/test_xrds/not-xrds.xml @@ -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 index 000000000..5522a6e5e --- /dev/null +++ b/vendor/gems/ruby-openid-2.1.4/test/data/test_xrds/prefixsometimes.xrds @@ -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 index 000000000..69cf683db --- /dev/null +++ b/vendor/gems/ruby-openid-2.1.4/test/data/test_xrds/ref.xrds @@ -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 index 000000000..eff755543 --- /dev/null +++ b/vendor/gems/ruby-openid-2.1.4/test/data/test_xrds/sometimesprefix.xrds @@ -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 index 000000000..8e870c815 --- /dev/null +++ b/vendor/gems/ruby-openid-2.1.4/test/data/test_xrds/spoof1.xrds @@ -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 index 000000000..7547561e1 --- /dev/null +++ b/vendor/gems/ruby-openid-2.1.4/test/data/test_xrds/spoof2.xrds @@ -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 index 000000000..f4c43c9b4 --- /dev/null +++ b/vendor/gems/ruby-openid-2.1.4/test/data/test_xrds/spoof3.xrds @@ -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 index 000000000..84cd5c906 --- /dev/null +++ b/vendor/gems/ruby-openid-2.1.4/test/data/test_xrds/status222.xrds @@ -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 index 000000000..11d2e9122 --- /dev/null +++ b/vendor/gems/ruby-openid-2.1.4/test/data/test_xrds/subsegments.xrds @@ -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 index 000000000..60e5ca7b1 --- /dev/null +++ b/vendor/gems/ruby-openid-2.1.4/test/data/test_xrds/valid-populated-xrds.xml @@ -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 index 000000000..736816570 --- /dev/null +++ b/vendor/gems/ruby-openid-2.1.4/test/data/trustroot.txt @@ -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 index 000000000..95262fe2d --- /dev/null +++ b/vendor/gems/ruby-openid-2.1.4/test/data/urinorm.txt @@ -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 index 000000000..7b21749f0 --- /dev/null +++ b/vendor/gems/ruby-openid-2.1.4/test/discoverdata.rb @@ -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 index 000000000..06db85bfa --- /dev/null +++ b/vendor/gems/ruby-openid-2.1.4/test/test_accept.rb @@ -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 index 000000000..1b273321b --- /dev/null +++ b/vendor/gems/ruby-openid-2.1.4/test/test_association.rb @@ -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 index 000000000..2a17a0a95 --- /dev/null +++ b/vendor/gems/ruby-openid-2.1.4/test/test_associationmanager.rb @@ -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 index 000000000..c1dcfb1f5 --- /dev/null +++ b/vendor/gems/ruby-openid-2.1.4/test/test_ax.rb @@ -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 index 000000000..e1d95c91b --- /dev/null +++ b/vendor/gems/ruby-openid-2.1.4/test/test_checkid_request.rb @@ -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 index 000000000..dc4a09e69 --- /dev/null +++ b/vendor/gems/ruby-openid-2.1.4/test/test_consumer.rb @@ -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 index 000000000..f6a38a0da --- /dev/null +++ b/vendor/gems/ruby-openid-2.1.4/test/test_cryptutil.rb @@ -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 index 000000000..397c15e53 --- /dev/null +++ b/vendor/gems/ruby-openid-2.1.4/test/test_dh.rb @@ -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 index 000000000..13b115ce1 --- /dev/null +++ b/vendor/gems/ruby-openid-2.1.4/test/test_discover.rb @@ -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 index 000000000..da966f384 --- /dev/null +++ b/vendor/gems/ruby-openid-2.1.4/test/test_discovery_manager.rb @@ -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 index 000000000..bfe14e9b2 --- /dev/null +++ b/vendor/gems/ruby-openid-2.1.4/test/test_extension.rb @@ -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 index 000000000..c30861cc4 --- /dev/null +++ b/vendor/gems/ruby-openid-2.1.4/test/test_extras.rb @@ -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 index 000000000..7130da55e --- /dev/null +++ b/vendor/gems/ruby-openid-2.1.4/test/test_fetchers.rb @@ -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 index 000000000..990201f02 --- /dev/null +++ b/vendor/gems/ruby-openid-2.1.4/test/test_filters.rb @@ -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 index 000000000..2abe45bbd --- /dev/null +++ b/vendor/gems/ruby-openid-2.1.4/test/test_idres.rb @@ -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 index 000000000..92260573d --- /dev/null +++ b/vendor/gems/ruby-openid-2.1.4/test/test_kvform.rb @@ -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 index 000000000..8f6480498 --- /dev/null +++ b/vendor/gems/ruby-openid-2.1.4/test/test_kvpost.rb @@ -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 index 000000000..6360d507e --- /dev/null +++ b/vendor/gems/ruby-openid-2.1.4/test/test_linkparse.rb @@ -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 index 000000000..f8ef91871 --- /dev/null +++ b/vendor/gems/ruby-openid-2.1.4/test/test_message.rb @@ -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 index 000000000..b8a1a054c --- /dev/null +++ b/vendor/gems/ruby-openid-2.1.4/test/test_nonce.rb @@ -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 index 000000000..fb8c71e6d --- /dev/null +++ b/vendor/gems/ruby-openid-2.1.4/test/test_openid_yadis.rb @@ -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 index 000000000..bd8289c4f --- /dev/null +++ b/vendor/gems/ruby-openid-2.1.4/test/test_pape.rb @@ -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 index 000000000..49542a6a6 --- /dev/null +++ b/vendor/gems/ruby-openid-2.1.4/test/test_parsehtml.rb @@ -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 index 000000000..61a0e4c19 --- /dev/null +++ b/vendor/gems/ruby-openid-2.1.4/test/test_responses.rb @@ -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 index 000000000..11d0f567d --- /dev/null +++ b/vendor/gems/ruby-openid-2.1.4/test/test_server.rb @@ -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 index 000000000..7438d5923 --- /dev/null +++ b/vendor/gems/ruby-openid-2.1.4/test/test_sreg.rb @@ -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 index 000000000..591062126 --- /dev/null +++ b/vendor/gems/ruby-openid-2.1.4/test/test_stores.rb @@ -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 index 000000000..2616021a0 --- /dev/null +++ b/vendor/gems/ruby-openid-2.1.4/test/test_trustroot.rb @@ -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 index 000000000..55c50e1c0 --- /dev/null +++ b/vendor/gems/ruby-openid-2.1.4/test/test_urinorm.rb @@ -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 index 000000000..ce1138ae3 --- /dev/null +++ b/vendor/gems/ruby-openid-2.1.4/test/test_util.rb @@ -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 index 000000000..ced780284 --- /dev/null +++ b/vendor/gems/ruby-openid-2.1.4/test/test_xrds.rb @@ -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 index 000000000..ba20e9c4b --- /dev/null +++ b/vendor/gems/ruby-openid-2.1.4/test/test_xri.rb @@ -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 index 000000000..3959361bb --- /dev/null +++ b/vendor/gems/ruby-openid-2.1.4/test/test_xrires.rb @@ -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 index 000000000..b0fdd178a --- /dev/null +++ b/vendor/gems/ruby-openid-2.1.4/test/test_yadis_discovery.rb @@ -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 index 000000000..7676fb953 --- /dev/null +++ b/vendor/gems/ruby-openid-2.1.4/test/testutil.rb @@ -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 index 000000000..93bd096cb --- /dev/null +++ b/vendor/gems/ruby-openid-2.1.4/test/util.rb @@ -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 + |