├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── CHANGES-2.0.0 ├── CHANGES-2.1.0 ├── Gemfile ├── INSTALL.md ├── LICENSE ├── NOTICE ├── README.md ├── Rakefile ├── UPGRADE.md ├── admin ├── build-docs ├── fixperms ├── gettlds.py ├── graph-require.sh ├── library-name ├── mkassoc ├── prepare-release └── runtests ├── contrib └── google │ ├── ruby-openid-apps-discovery-1.0.gem │ └── ruby-openid-apps-discovery-1.01.gem ├── examples ├── README.md ├── active_record_openid_store │ ├── README │ ├── XXX_add_open_id_store_to_db.rb │ ├── XXX_upgrade_open_id_store.rb │ ├── init.rb │ ├── lib │ │ ├── association.rb │ │ ├── nonce.rb │ │ ├── open_id_setting.rb │ │ └── openid_ar_store.rb │ └── test │ │ └── store_test.rb ├── discover └── rails_openid │ ├── Gemfile │ ├── README │ ├── README.rdoc │ ├── Rakefile │ ├── app │ ├── assets │ │ ├── images │ │ │ └── rails.png │ │ ├── javascripts │ │ │ └── application.js │ │ └── stylesheets │ │ │ └── application.css │ ├── controllers │ │ ├── application_controller.rb │ │ ├── consumer_controller.rb │ │ ├── login_controller.rb │ │ └── server_controller.rb │ ├── helpers │ │ ├── application_helper.rb │ │ ├── login_helper.rb │ │ └── server_helper.rb │ ├── mailers │ │ └── .gitkeep │ ├── models │ │ └── .gitkeep │ └── views │ │ ├── consumer │ │ └── index.html.erb │ │ ├── layouts │ │ └── server.html.erb │ │ ├── login │ │ └── index.html.erb │ │ └── server │ │ └── decide.html.erb │ ├── config.ru │ ├── config │ ├── application.rb │ ├── boot.rb │ ├── database.yml │ ├── environment.rb │ ├── environments │ │ ├── development.rb │ │ ├── production.rb │ │ └── test.rb │ ├── initializers │ │ ├── backtrace_silencers.rb │ │ ├── inflections.rb │ │ ├── mime_types.rb │ │ ├── rails_root.rb │ │ ├── secret_token.rb │ │ ├── session_store.rb │ │ └── wrap_parameters.rb │ ├── locales │ │ └── en.yml │ └── routes.rb │ ├── db │ ├── development.sqlite3 │ └── seeds.rb │ ├── doc │ └── README_FOR_APP │ ├── lib │ ├── assets │ │ └── .gitkeep │ └── tasks │ │ └── .gitkeep │ ├── log │ ├── .gitkeep │ └── development.log │ ├── public │ ├── 404.html │ ├── 422.html │ ├── 500.html │ ├── dispatch.cgi │ ├── dispatch.fcgi │ ├── dispatch.rb │ ├── favicon.ico │ ├── images │ │ └── openid_login_bg.gif │ ├── javascripts │ │ ├── application.js │ │ ├── controls.js │ │ ├── dragdrop.js │ │ ├── effects.js │ │ └── prototype.js │ └── robots.txt │ ├── script │ └── rails │ └── test │ ├── fixtures │ └── .gitkeep │ ├── functional │ ├── .gitkeep │ ├── login_controller_test.rb │ └── server_controller_test.rb │ ├── integration │ └── .gitkeep │ ├── performance │ └── browsing_test.rb │ ├── test_helper.rb │ └── unit │ └── .gitkeep ├── lib ├── hmac │ ├── hmac.rb │ ├── sha1.rb │ └── sha2.rb ├── openid.rb ├── openid │ ├── association.rb │ ├── consumer.rb │ ├── consumer │ │ ├── associationmanager.rb │ │ ├── checkid_request.rb │ │ ├── discovery.rb │ │ ├── discovery_manager.rb │ │ ├── html_parse.rb │ │ ├── idres.rb │ │ ├── responses.rb │ │ └── session.rb │ ├── cryptutil.rb │ ├── dh.rb │ ├── extension.rb │ ├── extensions │ │ ├── ax.rb │ │ ├── oauth.rb │ │ ├── pape.rb │ │ ├── sreg.rb │ │ └── ui.rb │ ├── fetchers.rb │ ├── kvform.rb │ ├── kvpost.rb │ ├── message.rb │ ├── protocolerror.rb │ ├── server.rb │ ├── store │ │ ├── filesystem.rb │ │ ├── interface.rb │ │ ├── memcache.rb │ │ ├── memory.rb │ │ └── nonce.rb │ ├── trustroot.rb │ ├── urinorm.rb │ ├── util.rb │ ├── version.rb │ └── yadis │ │ ├── accept.rb │ │ ├── constants.rb │ │ ├── discovery.rb │ │ ├── filters.rb │ │ ├── htmltokenizer.rb │ │ ├── parsehtml.rb │ │ ├── services.rb │ │ ├── xrds.rb │ │ ├── xri.rb │ │ └── xrires.rb └── ruby-openid.rb ├── ruby-openid.gemspec ├── setup.rb └── test ├── data ├── accept.txt ├── dh.txt ├── example-xrds.xml ├── linkparse.txt ├── n2b64 ├── test1-discover.txt ├── test1-parsehtml.txt ├── test_discover │ ├── malformed_meta_tag.html │ ├── openid.html │ ├── openid2.html │ ├── openid2_xrds.xml │ ├── openid2_xrds_no_local_id.xml │ ├── openid_1_and_2.html │ ├── openid_1_and_2_xrds.xml │ ├── openid_1_and_2_xrds_bad_delegate.xml │ ├── openid_and_yadis.html │ ├── openid_no_delegate.html │ ├── openid_utf8.html │ ├── yadis_0entries.xml │ ├── yadis_2_bad_local_id.xml │ ├── yadis_2entries_delegate.xml │ ├── yadis_2entries_idp.xml │ ├── yadis_another_delegate.xml │ ├── yadis_idp.xml │ ├── yadis_idp_delegate.xml │ └── yadis_no_delegate.xml ├── test_xrds │ ├── =j3h.2007.11.14.xrds │ ├── README │ ├── delegated-20060809-r1.xrds │ ├── delegated-20060809-r2.xrds │ ├── delegated-20060809.xrds │ ├── no-xrd.xml │ ├── not-xrds.xml │ ├── prefixsometimes.xrds │ ├── ref.xrds │ ├── sometimesprefix.xrds │ ├── spoof1.xrds │ ├── spoof2.xrds │ ├── spoof3.xrds │ ├── status222.xrds │ ├── subsegments.xrds │ └── valid-populated-xrds.xml ├── trustroot.txt └── urinorm.txt ├── discoverdata.rb ├── test_accept.rb ├── test_association.rb ├── test_associationmanager.rb ├── test_ax.rb ├── test_checkid_request.rb ├── test_consumer.rb ├── test_cryptutil.rb ├── test_dh.rb ├── test_discover.rb ├── test_discovery_manager.rb ├── test_extension.rb ├── test_fetchers.rb ├── test_filters.rb ├── test_idres.rb ├── test_kvform.rb ├── test_kvpost.rb ├── test_linkparse.rb ├── test_message.rb ├── test_nonce.rb ├── test_oauth.rb ├── test_openid_yadis.rb ├── test_pape.rb ├── test_parsehtml.rb ├── test_responses.rb ├── test_server.rb ├── test_sreg.rb ├── test_stores.rb ├── test_trustroot.rb ├── test_ui.rb ├── test_urinorm.rb ├── test_util.rb ├── test_xrds.rb ├── test_xri.rb ├── test_xrires.rb ├── test_yadis_discovery.rb ├── testutil.rb └── util.rb /.gitignore: -------------------------------------------------------------------------------- 1 | *.gem 2 | *.rbc 3 | .bundle 4 | .config 5 | .yardoc 6 | Gemfile.lock 7 | InstalledFiles 8 | _yardoc 9 | coverage 10 | doc/ 11 | lib/bundler/man 12 | pkg 13 | rdoc 14 | spec/reports 15 | test/tmp 16 | test/version_tmp 17 | tmp 18 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: ruby 2 | sudo: required 3 | dist: trusty 4 | script: rake 5 | rvm: 6 | - "1.9" 7 | - "2.0" 8 | - "2.1" 9 | - "2.2" 10 | - "2.3" 11 | - "2.4" 12 | - "2.5" 13 | - "2.6" 14 | - ruby-head 15 | - jruby 16 | - jruby-head 17 | - jruby-19mode 18 | - rubinius-3 19 | 20 | before_install: 21 | - "gem install bundler || gem install bundler --version '< 2'" 22 | 23 | matrix: 24 | allow_failures: 25 | - rvm: "ruby-head" 26 | - rvm: "jruby-head" 27 | -------------------------------------------------------------------------------- /CHANGES-2.0.0: -------------------------------------------------------------------------------- 1 | 2 | * API Changes 3 | * PAPE (Provider Authentication Policy Extension) module 4 | * Updated extension for specification draft 2 5 | * PAPE::Request::from_success_response returns nil if PAPE 6 | response arguments were not signed 7 | * Added functions to generate request/response HTML forms with 8 | auto-submission javascript 9 | * Consumer (relying party) API: 10 | Auth_OpenID_AuthRequest::htmlMarkup 11 | * Server API: Auth_OpenID_OpenIDResponse::toHTML 12 | * Removed Rails login generator 13 | * SReg::Response::from_success_response returns nil when no signed 14 | arguments were found 15 | 16 | * New Features 17 | * Fetchers now only read/request first megabyte of response 18 | 19 | * Bug fixes 20 | * NOT NULL constraints to tables created by ActiveRecordStore 21 | * check_authentication requests: copy entire response, not just 22 | signed fields. Fixes missing namespace in check_authentication 23 | requests 24 | * OpenID 1 association requests no longer explicitly set 25 | no-encryption session type 26 | * Improved HTML parsing 27 | * AssociationRequest::answer: include session_type in 28 | no-encryption assoc responses 29 | * normalize return_to URL before performing return_to verification 30 | * OpenID::Consumer::IdResHandler.verify_discovery_results_openid1: 31 | fall back to OpenID 1.0 type if 1.1 endpoint cannot be found 32 | * StandardFetcher now includes a timeout setting 33 | * Handle blank content types in 34 | OpenID::Yadis::DiscoveryResult.where_is_yadis? 35 | * Properly convert timestamps to ints before storing in DB, and vise 36 | versa 37 | -------------------------------------------------------------------------------- /CHANGES-2.1.0: -------------------------------------------------------------------------------- 1 | 2 | * API Changes 3 | * PAPE (Provider Authentication Policy Extension) module 4 | * Updated extension for specification draft 2 5 | * PAPE::Request::from_success_response returns nil if PAPE 6 | response arguments were not signed 7 | * Added functions to generate request/response HTML forms with 8 | auto-submission javascript 9 | * Consumer (relying party) API: 10 | Auth_OpenID_AuthRequest::htmlMarkup 11 | * Server API: Auth_OpenID_OpenIDResponse::toHTML 12 | * Removed Rails login generator 13 | * SReg::Response::from_success_response returns nil when no signed 14 | arguments were found 15 | 16 | * New Features 17 | * Fetchers now only read/request first megabyte of response 18 | 19 | * Bug fixes 20 | * NOT NULL constraints to tables created by ActiveRecordStore 21 | * check_authentication requests: copy entire response, not just 22 | signed fields. Fixes missing namespace in check_authentication 23 | requests 24 | * OpenID 1 association requests no longer explicitly set 25 | no-encryption session type 26 | * Improved HTML parsing 27 | * AssociationRequest::answer: include session_type in 28 | no-encryption assoc responses 29 | * normalize return_to URL before performing return_to verification 30 | * OpenID::Consumer::IdResHandler.verify_discovery_results_openid1: 31 | fall back to OpenID 1.0 type if 1.1 endpoint cannot be found 32 | * StandardFetcher now includes a timeout setting 33 | * Handle blank content types in 34 | OpenID::Yadis::DiscoveryResult.where_is_yadis? 35 | * Properly convert timestamps to ints before storing in DB, and vise 36 | versa 37 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | # Specify your gem's dependencies in ruby-openid.gemspec 4 | gemspec 5 | 6 | gem 'rake' 7 | -------------------------------------------------------------------------------- /INSTALL.md: -------------------------------------------------------------------------------- 1 | # Ruby OpenID Library Installation 2 | 3 | ## Install as a gem 4 | 5 | `ruby-openid` is distributed on [RubyGems](https://rubygems.org/). 6 | Install it: 7 | 8 | gem install ruby-openid 9 | 10 | This is probably what you need. 11 | 12 | ## Manual Installation 13 | 14 | Unpack the archive and run `setup.rb` to install: 15 | 16 | ruby setup.rb 17 | 18 | `setup.rb` installs the library into your system ruby. If don't want to 19 | add openid to you system ruby, you may instead add the `lib` directory of 20 | the extracted tarball to your `RUBYLIB` environment variable: 21 | 22 | $ export RUBYLIB=${RUBYLIB}:/path/to/ruby-openid/lib 23 | 24 | ## Testing the Installation 25 | 26 | Make sure everything installed ok: 27 | 28 | $> irb 29 | irb$> require "openid" 30 | => true 31 | 32 | ## Run the test suite 33 | 34 | Go into the test directory and execute the `runtests.rb` script. 35 | 36 | ## Next steps 37 | 38 | * Run `consumer.rb` in the `examples/` directory. 39 | * Get started writing your own consumer using `OpenID::Consumer` 40 | * Write your own server with `OpenID::Server` 41 | * Use the `OpenIDLoginGenerator`! Read `examples/README.md` for more info. 42 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | This product includes software developed by JanRain, 2 | available from http://github.com/openid/ruby-openid 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Ruby OpenID 2 | 3 | A Ruby library for verifying and serving OpenID identities. 4 | 5 | [![Build Status](https://secure.travis-ci.org/openid/ruby-openid.svg)](http://travis-ci.org/openid/ruby-openid) 6 | 7 | ## Features 8 | 9 | * Easy to use API for verifying OpenID identites - OpenID::Consumer 10 | * Support for serving OpenID identites - OpenID::Server 11 | * Does not depend on underlying web framework 12 | * Supports multiple storage mechanisms (Filesystem, ActiveRecord, Memory) 13 | * Example code to help you get started, including: 14 | * Ruby on Rails based consumer and server 15 | * OpenIDLoginGenerator for quickly getting creating a rails app that uses 16 | OpenID for authentication 17 | * ActiveRecordOpenIDStore plugin 18 | * Comprehensive test suite 19 | * Supports both OpenID 1 and OpenID 2 transparently 20 | 21 | ## Installing 22 | 23 | Before running the examples or writing your own code you'll need to install 24 | the library. See the INSTALL file or use rubygems: 25 | 26 | gem install ruby-openid 27 | 28 | Check the installation: 29 | 30 | $ irb 31 | irb> require 'rubygems' 32 | => false 33 | irb> gem 'ruby-openid' 34 | => true 35 | 36 | The library is known to work with Ruby 1.9.2 and above on Unix, Max OS X and Win32. 37 | 38 | ## Getting Started 39 | 40 | The best way to start is to look at the rails_openid example. 41 | You can run it with: 42 | 43 | cd examples/rails_openid 44 | script/server 45 | 46 | If you are writing an OpenID Relying Party, a good place to start is: 47 | `examples/rails_openid/app/controllers/consumer_controller.rb` 48 | 49 | And if you are writing an OpenID provider: 50 | `examples/rails_openid/app/controllers/server_controller.rb` 51 | 52 | The library code is quite well documented, so don't be squeamish, and 53 | look at the library itself if there's anything you don't understand in 54 | the examples. 55 | 56 | ## Homepage 57 | 58 | * GitHub repository: [openid/ruby-openid](http://github.com/openid/ruby-openid) 59 | * Homepage: [OpenID.net](http://openid.net/) 60 | 61 | ## Community 62 | 63 | Discussion regarding the Ruby OpenID library and other JanRain OpenID 64 | libraries takes place on the [OpenID mailing list](http://openid.net/developers/dev-mailing-lists/). 65 | 66 | Please join this list to discuss, ask implementation questions, report 67 | bugs, etc. Also check out the openid channel on the freenode IRC 68 | network. 69 | 70 | If you have a bugfix or feature you'd like to contribute, don't 71 | hesitate to send it to us: [How to contribute](http://openidenabled.com/contribute/). 72 | 73 | ## Author 74 | 75 | Copyright 2006-2012, JanRain, Inc. 76 | 77 | Contact openid@janrain.com. 78 | 79 | ## License 80 | 81 | Apache Software License. For more information see the LICENSE file. 82 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env rake 2 | require 'bundler/gem_tasks' 3 | 4 | require 'rake/testtask' 5 | 6 | desc "Run tests" 7 | Rake::TestTask.new('test') do |t| 8 | t.libs << 'lib' 9 | t.libs << 'test' 10 | t.test_files = FileList["test/**/test_*.rb"] 11 | t.verbose = false 12 | end 13 | 14 | task :default => :test 15 | -------------------------------------------------------------------------------- /admin/build-docs: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # 3 | # Build the HTML documentation for the JanRain PHP OpenID library 4 | # 5 | # Usage: 6 | # build-docs 7 | # 8 | # Must be run from the base of the repository 9 | 10 | RDOC_FILES="README INSTALL LICENSE UPGRADE lib/openid examples/README" 11 | MAIN=README 12 | rdoc --main="$MAIN" $RDOC_FILES -------------------------------------------------------------------------------- /admin/fixperms: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | cat < 60: 41 | print output_line + line_suffix 42 | output_line = line_prefix + tld 43 | else: 44 | output_line = new_output_line 45 | prefix = separator 46 | 47 | print output_line + suffix 48 | -------------------------------------------------------------------------------- /admin/graph-require.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | OUTPUT_FILE="deps.png" 4 | 5 | if [ ! "$1" ] ; then 6 | echo "Usage: graph-require.sh [output_filename]" 7 | exit 1 8 | fi 9 | 10 | if [ "$2" ] ; then 11 | OUTPUT_FILE=$2 12 | fi 13 | 14 | grep -r '^ *require ['"'"'"]' $1 > require.txt 15 | 16 | python < CHANGELOG 24 | 25 | -------------------------------------------------------------------------------- /admin/runtests: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | case "$1" in 4 | --coverage) 5 | shift 6 | RUBY="rcov --exclude=^lib/hmac/,^admin/ --sort=coverage" 7 | ;; 8 | *) 9 | RUBY="ruby" 10 | ;; 11 | esac 12 | 13 | HERE=$(dirname $(readlink --canonicalize "$0")) 14 | REPOROOT=$(dirname "$HERE") 15 | TESTING_MEMCACHE="localhost:11211" RUBYLIB="$REPOROOT/lib" $RUBY "$@" "$REPOROOT/admin/runtests.rb" 16 | -------------------------------------------------------------------------------- /contrib/google/ruby-openid-apps-discovery-1.0.gem: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openid/ruby-openid/13a88ad6442133a613d2b7d6601991a84b34630d/contrib/google/ruby-openid-apps-discovery-1.0.gem -------------------------------------------------------------------------------- /contrib/google/ruby-openid-apps-discovery-1.01.gem: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openid/ruby-openid/13a88ad6442133a613d2b7d6601991a84b34630d/contrib/google/ruby-openid-apps-discovery-1.01.gem -------------------------------------------------------------------------------- /examples/README.md: -------------------------------------------------------------------------------- 1 | This directory contains several examples that demonstrate use of the 2 | OpenID library. Make sure you have properly installed the library 3 | before running the examples. These examples are a great place to 4 | start in integrating OpenID into your application. 5 | 6 | ## Rails example 7 | 8 | The `rails_openid` directory contains a fully functional OpenID server and relying 9 | party, and acts as a starting point for implementing your own 10 | production rails server. You'll need the latest version of Ruby on 11 | Rails installed, and then: 12 | 13 | ```shell 14 | cd rails_openid 15 | ./script/server 16 | ``` 17 | 18 | Open a web browser to http://localhost:3000/ and follow the instructions. 19 | 20 | The relevant code to work from when writing your Rails OpenID Relying 21 | Party is: 22 | 23 | rails_openid/app/controllers/consumer_controller.rb 24 | 25 | If you are working on an OpenID provider, check out 26 | 27 | rails_openid/app/controllers/server_controller.rb 28 | 29 | Since the library and examples are Apache-licensed, don't be shy about 30 | copy-and-paste. 31 | 32 | ## Rails ActiveRecord OpenIDStore plugin 33 | 34 | For various reasons you may want or need to deploy your ruby openid 35 | consumer/server using an SQL based store. The `active_record_openid_store` 36 | is a plugin that makes using an SQL based store simple. Follow the 37 | README inside the plugin's dir for usage. 38 | -------------------------------------------------------------------------------- /examples/active_record_openid_store/README: -------------------------------------------------------------------------------- 1 | =Active Record OpenID Store Plugin 2 | 3 | A store is required by an OpenID server and optionally by the consumer 4 | to store associations, nonces, and auth key information across 5 | requests and processes. If rails is distributed across several 6 | machines, they must must all have access to the same OpenID store 7 | data, so the FilesystemStore won't do. 8 | 9 | This directory contains a plugin for connecting your 10 | OpenID enabled rails app to an ActiveRecord based OpenID store. 11 | 12 | ==Install 13 | 14 | 1) Copy this directory and all it's contents into your 15 | RAILS_ROOT/vendor/plugins directory. You structure should look like 16 | this: 17 | 18 | RAILS_ROOT/vendor/plugins/active_record_openid_store/ 19 | 20 | 2) Copy the migration, XXX_add_open_id_store_to_db.rb to your 21 | RAILS_ROOT/db/migrate directory. Rename the XXX portion of the 22 | file to next sequential migration number. 23 | 24 | 3) Run the migration: 25 | 26 | rake migrate 27 | 28 | 4) Change your app to use the ActiveRecordOpenIDStore: 29 | 30 | store = ActiveRecordOpenIDStore.new 31 | consumer = OpenID::Consumer.new(session, store) 32 | 33 | 5) That's it! All your OpenID state will now be stored in the database. 34 | 35 | ==Upgrade 36 | 37 | If you are upgrading from the 1.x ActiveRecord store, replace your old 38 | RAILS_ROOT/vendor/plugins/active_record_openid_store/ directory with 39 | the new one and run the migration XXX_upgrade_open_id_store.rb. 40 | 41 | ==What about garbage collection? 42 | 43 | You may garbage collect unused nonces and expired associations using 44 | the gc instance method of ActiveRecordOpenIDStore. Hook it up to a 45 | task in your app's Rakefile like so: 46 | 47 | desc 'GC OpenID store, deleting expired nonces and associations' 48 | task :gc_openid_store => :environment do 49 | require 'openid_ar_store' 50 | nonces, associations = ActiveRecordStore.new.cleanup 51 | puts "Deleted #{nonces} nonces, #{associations} associations" 52 | end 53 | 54 | Run it by typing: 55 | 56 | rake gc_openid_store 57 | 58 | 59 | ==Questions? 60 | Contact Dag Arneson: dag at janrain dot com 61 | -------------------------------------------------------------------------------- /examples/active_record_openid_store/XXX_add_open_id_store_to_db.rb: -------------------------------------------------------------------------------- 1 | # Use this migration to create the tables for the ActiveRecord store 2 | class AddOpenIdStoreToDb < ActiveRecord::Migration 3 | def self.up 4 | create_table "open_id_associations", :force => true do |t| 5 | t.column "server_url", :string, :null => false 6 | t.column "handle", :string, :null => false 7 | t.column "secret", :binary, :null => false 8 | t.column "issued", :integer, :null => false 9 | t.column "lifetime", :integer, :null => false 10 | t.column "assoc_type", :string, :null => false 11 | end 12 | 13 | create_table "open_id_nonces", :force => true do |t| 14 | t.column :server_url, :string, :null => false 15 | t.column :timestamp, :integer, :null => false 16 | t.column :salt, :string, :null => false 17 | end 18 | end 19 | 20 | def self.down 21 | drop_table "open_id_associations" 22 | drop_table "open_id_nonces" 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /examples/active_record_openid_store/XXX_upgrade_open_id_store.rb: -------------------------------------------------------------------------------- 1 | # Use this migration to upgrade the old 1.1 ActiveRecord store schema 2 | # to the new 2.0 schema. 3 | class UpgradeOpenIdStore < ActiveRecord::Migration 4 | def self.up 5 | drop_table "open_id_settings" 6 | drop_table "open_id_nonces" 7 | create_table "open_id_nonces", :force => true do |t| 8 | t.column :server_url, :string, :null => false 9 | t.column :timestamp, :integer, :null => false 10 | t.column :salt, :string, :null => false 11 | end 12 | end 13 | 14 | def self.down 15 | drop_table "open_id_nonces" 16 | create_table "open_id_nonces", :force => true do |t| 17 | t.column "nonce", :string 18 | t.column "created", :integer 19 | end 20 | 21 | create_table "open_id_settings", :force => true do |t| 22 | t.column "setting", :string 23 | t.column "value", :binary 24 | end 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /examples/active_record_openid_store/init.rb: -------------------------------------------------------------------------------- 1 | # might using the ruby-openid gem 2 | begin 3 | require 'rubygems' 4 | rescue LoadError 5 | nil 6 | end 7 | require 'openid' 8 | require 'openid_ar_store' 9 | -------------------------------------------------------------------------------- /examples/active_record_openid_store/lib/association.rb: -------------------------------------------------------------------------------- 1 | require 'openid/association' 2 | require 'time' 3 | 4 | class Association < ActiveRecord::Base 5 | set_table_name 'open_id_associations' 6 | def from_record 7 | OpenID::Association.new(handle, secret, Time.at(issued), lifetime, assoc_type) 8 | end 9 | end 10 | 11 | -------------------------------------------------------------------------------- /examples/active_record_openid_store/lib/nonce.rb: -------------------------------------------------------------------------------- 1 | class Nonce < ActiveRecord::Base 2 | set_table_name 'open_id_nonces' 3 | end 4 | -------------------------------------------------------------------------------- /examples/active_record_openid_store/lib/open_id_setting.rb: -------------------------------------------------------------------------------- 1 | class OpenIdSetting < ActiveRecord::Base 2 | 3 | validates_uniqueness_of :setting 4 | end 5 | -------------------------------------------------------------------------------- /examples/active_record_openid_store/lib/openid_ar_store.rb: -------------------------------------------------------------------------------- 1 | require 'association' 2 | require 'nonce' 3 | require 'openid/store/interface' 4 | 5 | # not in OpenID module to avoid namespace conflict 6 | class ActiveRecordStore < OpenID::Store::Interface 7 | def store_association(server_url, assoc) 8 | remove_association(server_url, assoc.handle) 9 | Association.create!(:server_url => server_url, 10 | :handle => assoc.handle, 11 | :secret => assoc.secret, 12 | :issued => assoc.issued.to_i, 13 | :lifetime => assoc.lifetime, 14 | :assoc_type => assoc.assoc_type) 15 | end 16 | 17 | def get_association(server_url, handle=nil) 18 | assocs = if handle.blank? 19 | Association.find_all_by_server_url(server_url) 20 | else 21 | Association.find_all_by_server_url_and_handle(server_url, handle) 22 | end 23 | 24 | assocs.reverse.each do |assoc| 25 | a = assoc.from_record 26 | if a.expires_in == 0 27 | assoc.destroy 28 | else 29 | return a 30 | end 31 | end if assocs.any? 32 | 33 | return nil 34 | end 35 | 36 | def remove_association(server_url, handle) 37 | Association.delete_all(['server_url = ? AND handle = ?', server_url, handle]) > 0 38 | end 39 | 40 | def use_nonce(server_url, timestamp, salt) 41 | return false if Nonce.find_by_server_url_and_timestamp_and_salt(server_url, timestamp, salt) 42 | return false if (timestamp - Time.now.to_i).abs > OpenID::Nonce.skew 43 | Nonce.create!(:server_url => server_url, :timestamp => timestamp, :salt => salt) 44 | return true 45 | end 46 | 47 | def cleanup_nonces 48 | now = Time.now.to_i 49 | Nonce.delete_all(["timestamp > ? OR timestamp < ?", now + OpenID::Nonce.skew, now - OpenID::Nonce.skew]) 50 | end 51 | 52 | def cleanup_associations 53 | now = Time.now.to_i 54 | Association.delete_all(['issued + lifetime < ?',now]) 55 | end 56 | 57 | end 58 | -------------------------------------------------------------------------------- /examples/discover: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require "openid/consumer/discovery" 3 | require 'openid/fetchers' 4 | 5 | OpenID::fetcher_use_env_http_proxy 6 | 7 | $names = [[:server_url, "Server URL "], 8 | [:local_id, "Local ID "], 9 | [:canonical_id, "Canonical ID"], 10 | ] 11 | 12 | def show_services(user_input, normalized, services) 13 | puts " Claimed identifier: #{normalized}" 14 | if services.empty? 15 | puts " No OpenID services found" 16 | puts 17 | else 18 | puts " Discovered services:" 19 | n = 0 20 | services.each do |service| 21 | n += 1 22 | puts " #{n}." 23 | $names.each do |meth, name| 24 | val = service.send(meth) 25 | if val 26 | printf(" %s: %s\n", name, val) 27 | end 28 | end 29 | puts " Type URIs:" 30 | for type_uri in service.type_uris 31 | puts " * #{type_uri}" 32 | end 33 | puts 34 | end 35 | end 36 | end 37 | 38 | ARGV.each do |openid_identifier| 39 | puts "=" * 50 40 | puts "Running discovery on #{openid_identifier}" 41 | begin 42 | normalized_identifier, services = OpenID.discover(openid_identifier) 43 | rescue OpenID::DiscoveryFailure => why 44 | puts "Discovery failed: #{why.message}" 45 | puts 46 | else 47 | show_services(openid_identifier, normalized_identifier, services) 48 | end 49 | end 50 | -------------------------------------------------------------------------------- /examples/rails_openid/Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gem 'rails', '3.2.13' 4 | 5 | # Bundle edge Rails instead: 6 | # gem 'rails', :git => 'git://github.com/rails/rails.git' 7 | 8 | gem 'sqlite3' 9 | 10 | gem 'json' 11 | 12 | # Gems used only for assets and not required 13 | # in production environments by default. 14 | group :assets do 15 | gem 'sass-rails', '~> 3.2.3' 16 | gem 'coffee-rails', '~> 3.2.1' 17 | 18 | # See https://github.com/sstephenson/execjs#readme for more supported runtimes 19 | # gem 'therubyracer', :platforms => :ruby 20 | 21 | gem 'uglifier', '>= 1.0.3' 22 | end 23 | 24 | gem 'jquery-rails' 25 | 26 | # To use ActiveModel has_secure_password 27 | # gem 'bcrypt-ruby', '~> 3.0.0' 28 | 29 | # To use Jbuilder templates for JSON 30 | # gem 'jbuilder' 31 | 32 | # Use unicorn as the app server 33 | # gem 'unicorn' 34 | 35 | # Deploy with Capistrano 36 | # gem 'capistrano' 37 | 38 | # To use debugger 39 | # gem 'ruby-debug' 40 | 41 | gem 'ruby-openid', :require => 'openid' 42 | -------------------------------------------------------------------------------- /examples/rails_openid/Rakefile: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env rake 2 | # Add your own tasks in files placed in lib/tasks ending in .rake, 3 | # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. 4 | 5 | require File.expand_path('../config/application', __FILE__) 6 | 7 | RailsOpenid::Application.load_tasks 8 | -------------------------------------------------------------------------------- /examples/rails_openid/app/assets/images/rails.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openid/ruby-openid/13a88ad6442133a613d2b7d6601991a84b34630d/examples/rails_openid/app/assets/images/rails.png -------------------------------------------------------------------------------- /examples/rails_openid/app/assets/javascripts/application.js: -------------------------------------------------------------------------------- 1 | // This is a manifest file that'll be compiled into application.js, which will include all the files 2 | // listed below. 3 | // 4 | // Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts, 5 | // or vendor/assets/javascripts of plugins, if any, can be referenced here using a relative path. 6 | // 7 | // It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the 8 | // the compiled file. 9 | // 10 | // WARNING: THE FIRST BLANK LINE MARKS THE END OF WHAT'S TO BE PROCESSED, ANY BLANK LINE SHOULD 11 | // GO AFTER THE REQUIRES BELOW. 12 | // 13 | //= require jquery 14 | //= require jquery_ujs 15 | //= require_tree . 16 | -------------------------------------------------------------------------------- /examples/rails_openid/app/assets/stylesheets/application.css: -------------------------------------------------------------------------------- 1 | /* 2 | * This is a manifest file that'll be compiled into application.css, which will include all the files 3 | * listed below. 4 | * 5 | * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets, 6 | * or vendor/assets/stylesheets of plugins, if any, can be referenced here using a relative path. 7 | * 8 | * You're free to add application-wide styles to this file and they'll appear at the top of the 9 | * compiled file, but it's generally better to create a new file per style scope. 10 | * 11 | *= require_self 12 | *= require_tree . 13 | */ 14 | -------------------------------------------------------------------------------- /examples/rails_openid/app/controllers/application_controller.rb: -------------------------------------------------------------------------------- 1 | class ApplicationController < ActionController::Base 2 | protect_from_forgery 3 | end 4 | -------------------------------------------------------------------------------- /examples/rails_openid/app/controllers/login_controller.rb: -------------------------------------------------------------------------------- 1 | # Controller for handling the login, logout process for "users" of our 2 | # little server. Users have no password. This is just an example. 3 | 4 | require 'openid' 5 | 6 | class LoginController < ApplicationController 7 | 8 | layout 'server' 9 | 10 | def base_url 11 | url_for(:controller => 'login', :action => nil, :only_path => false) 12 | end 13 | 14 | def index 15 | response.headers['X-XRDS-Location'] = url_for(:controller => "server", 16 | :action => "idp_xrds", 17 | :only_path => false) 18 | @base_url = base_url 19 | # just show the login page 20 | end 21 | 22 | def submit 23 | user = params[:username] 24 | 25 | # if we get a user, log them in by putting their username in 26 | # the session hash. 27 | unless user.nil? 28 | session[:username] = user unless user.nil? 29 | session[:approvals] = [] 30 | flash[:notice] = "Your OpenID URL is #{base_url}user/#{user}

Proceed to step 2 below." 31 | else 32 | flash[:error] = "Sorry, couldn't log you in. Try again." 33 | end 34 | 35 | redirect_to :action => 'index' 36 | end 37 | 38 | def logout 39 | # delete the username from the session hash 40 | session[:username] = nil 41 | session[:approvals] = nil 42 | redirect_to :action => 'index' 43 | end 44 | 45 | end 46 | -------------------------------------------------------------------------------- /examples/rails_openid/app/helpers/application_helper.rb: -------------------------------------------------------------------------------- 1 | module ApplicationHelper 2 | end 3 | -------------------------------------------------------------------------------- /examples/rails_openid/app/helpers/login_helper.rb: -------------------------------------------------------------------------------- 1 | module LoginHelper 2 | end 3 | -------------------------------------------------------------------------------- /examples/rails_openid/app/helpers/server_helper.rb: -------------------------------------------------------------------------------- 1 | 2 | module ServerHelper 3 | 4 | def url_for_user 5 | url_for :controller => 'user', :action => session[:username] 6 | end 7 | 8 | end 9 | 10 | -------------------------------------------------------------------------------- /examples/rails_openid/app/mailers/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openid/ruby-openid/13a88ad6442133a613d2b7d6601991a84b34630d/examples/rails_openid/app/mailers/.gitkeep -------------------------------------------------------------------------------- /examples/rails_openid/app/models/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openid/ruby-openid/13a88ad6442133a613d2b7d6601991a84b34630d/examples/rails_openid/app/models/.gitkeep -------------------------------------------------------------------------------- /examples/rails_openid/app/views/consumer/index.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | Rails OpenID Example Relying Party 4 | 5 | 41 | 42 |

Rails OpenID Example Relying Party

43 | <% if flash[:alert] %> 44 |
45 | <%= h(flash[:alert]) %> 46 |
47 | <% end %> 48 | <% if flash[:error] %> 49 |
50 | <%= h(flash[:error]) %> 51 |
52 | <% end %> 53 | <% if flash[:success] %> 54 |
55 | <%= h(flash[:success]) %> 56 |
57 | <% end %> 58 | <% if flash[:sreg_results] %> 59 |
60 | <%= flash[:sreg_results] %> 61 |
62 | <% end %> 63 | <% if flash[:pape_results] %> 64 |
65 | <%= flash[:pape_results] %> 66 |
67 | <% end %> 68 |
69 |
'> 71 | Identifier: 72 | 73 |
74 |
75 |
76 |
77 | 78 |
79 |
80 | 81 | 82 | -------------------------------------------------------------------------------- /examples/rails_openid/app/views/layouts/server.html.erb: -------------------------------------------------------------------------------- 1 | 2 | OpenID Server Example 3 | <%#= csrf_meta_tags %> 4 | 5 | 45 | 46 | 47 | 48 | 49 | <% if session[:username] %> 50 |
51 | Welcome, <%= session[:username] %> | <%= link_to('Log out', :controller => 'login', :action => 'logout') %>
52 | <%= @base_url %>user/<%= session[:username] %> 53 |
54 | <% end %> 55 | 56 |

Ruby OpenID Server Example

57 | 58 |
59 | 60 | <% if flash[:notice] or flash[:error] %> 61 |
62 | <%= flash[:error] or flash[:notice] %> 63 |
64 | <% end %> 65 | 66 | <%= yield %> 67 | 68 | 69 | 70 | 71 | -------------------------------------------------------------------------------- /examples/rails_openid/app/views/login/index.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | <% if session[:username].nil? %> 4 | 5 |
6 |
7 | Type a username: 8 | 9 | 10 |
11 | 12 |
13 | 14 | <% end %> 15 | 16 |

Welcome to the Ruby OpenID example. This code is a starting point 17 | for developers wishing to implement an OpenID provider or relying 18 | party. We've used the Rails 19 | platform to demonstrate, but the library code is not Rails specific.

20 | 21 |

To use the example provider

22 |

23 |

    24 | 25 |
  1. Enter a username in the form above. You will be "Logged In" 26 | to the server, at which point you may authenticate using an OpenID 27 | consumer. Your OpenID URL will be displayed after you log 28 | in.

    The server will automatically create an identity page for 29 | you at <%= @base_url %>user/name

  2. 30 | 31 |
  3. Because WEBrick can only handle one thing at a time, you'll need to 32 | run another instance of the example on another port if you want to use 33 | a relying party to use with this example provider:

    34 |
    35 | script/server --port=3001 36 |
    37 | 38 |

    (The RP needs to be able to access the provider, so unless you're 39 | running this example on a public IP, you can't use the live example 40 | at openidenabled.com on 41 | your local provider.)

    42 |
  4. 43 | 44 |
  5. Point your browser to this new instance and follow the directions 45 | below.
  6. 46 | 47 |
48 | 49 |

50 | 51 |

To use the example relying party

52 | 53 |

Visit /consumer 54 | and enter your OpenID.

55 |

56 | 57 | -------------------------------------------------------------------------------- /examples/rails_openid/app/views/server/decide.html.erb: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 | 5 | 6 | 7 | <% if @oidreq.id_select %> 8 | 9 | 14 | 15 | 16 | 17 | 18 | 19 | <% else %> 20 | 21 | <% end %> 22 |
Site:<%= @oidreq.trust_root %>
10 | You entered the server identifier at the relying party. 11 | You'll need to send an identifier of your choosing. Enter a 12 | username below. 13 |
Identity to send:
Identity:<%= @oidreq.identity %>
23 | 24 | 25 | 26 | 27 |
28 | -------------------------------------------------------------------------------- /examples/rails_openid/config.ru: -------------------------------------------------------------------------------- 1 | # This file is used by Rack-based servers to start the application. 2 | 3 | require ::File.expand_path('../config/environment', __FILE__) 4 | run RailsOpenid::Application 5 | -------------------------------------------------------------------------------- /examples/rails_openid/config/application.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path('../boot', __FILE__) 2 | 3 | require 'rails/all' 4 | 5 | if defined?(Bundler) 6 | # If you precompile assets before deploying to production, use this line 7 | Bundler.require(*Rails.groups(:assets => %w(development test))) 8 | # If you want your assets lazily compiled in production, use this line 9 | # Bundler.require(:default, :assets, Rails.env) 10 | end 11 | 12 | module RailsOpenid 13 | class Application < Rails::Application 14 | # Settings in config/environments/* take precedence over those specified here. 15 | # Application configuration should go into files in config/initializers 16 | # -- all .rb files in that directory are automatically loaded. 17 | 18 | # Custom directories with classes and modules you want to be autoloadable. 19 | # config.autoload_paths += %W(#{config.root}/extras) 20 | 21 | # Only load the plugins named here, in the order given (default is alphabetical). 22 | # :all can be used as a placeholder for all plugins not explicitly named. 23 | # config.plugins = [ :exception_notification, :ssl_requirement, :all ] 24 | 25 | # Activate observers that should always be running. 26 | # config.active_record.observers = :cacher, :garbage_collector, :forum_observer 27 | 28 | # Set Time.zone default to the specified zone and make Active Record auto-convert to this zone. 29 | # Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC. 30 | # config.time_zone = 'Central Time (US & Canada)' 31 | 32 | # The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded. 33 | # config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s] 34 | # config.i18n.default_locale = :de 35 | 36 | # Configure the default encoding used in templates for Ruby 1.9. 37 | config.encoding = "utf-8" 38 | 39 | # Configure sensitive parameters which will be filtered from the log file. 40 | config.filter_parameters += [:password] 41 | 42 | # Enable escaping HTML in JSON. 43 | config.active_support.escape_html_entities_in_json = true 44 | 45 | # Use SQL instead of Active Record's schema dumper when creating the database. 46 | # This is necessary if your schema can't be completely dumped by the schema dumper, 47 | # like if you have constraints or database-specific column types 48 | # config.active_record.schema_format = :sql 49 | 50 | # Enforce whitelist mode for mass assignment. 51 | # This will create an empty whitelist of attributes available for mass-assignment for all models 52 | # in your app. As such, your models will need to explicitly whitelist or blacklist accessible 53 | # parameters by using an attr_accessible or attr_protected declaration. 54 | config.active_record.whitelist_attributes = true 55 | 56 | # Enable the asset pipeline 57 | config.assets.enabled = true 58 | 59 | # Version of your assets, change this if you want to expire all your assets 60 | config.assets.version = '1.0' 61 | end 62 | end 63 | -------------------------------------------------------------------------------- /examples/rails_openid/config/boot.rb: -------------------------------------------------------------------------------- 1 | require 'rubygems' 2 | 3 | # Set up gems listed in the Gemfile. 4 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__) 5 | 6 | require 'bundler/setup' if File.exists?(ENV['BUNDLE_GEMFILE']) 7 | -------------------------------------------------------------------------------- /examples/rails_openid/config/database.yml: -------------------------------------------------------------------------------- 1 | # SQLite version 3.x 2 | # gem install sqlite3 3 | # 4 | # Ensure the SQLite 3 gem is defined in your Gemfile 5 | # gem 'sqlite3' 6 | development: 7 | adapter: sqlite3 8 | database: db/development.sqlite3 9 | pool: 5 10 | timeout: 5000 11 | 12 | # Warning: The database defined as "test" will be erased and 13 | # re-generated from your development database when you run "rake". 14 | # Do not set this db to the same as development or production. 15 | test: 16 | adapter: sqlite3 17 | database: db/test.sqlite3 18 | pool: 5 19 | timeout: 5000 20 | 21 | production: 22 | adapter: sqlite3 23 | database: db/production.sqlite3 24 | pool: 5 25 | timeout: 5000 26 | -------------------------------------------------------------------------------- /examples/rails_openid/config/environment.rb: -------------------------------------------------------------------------------- 1 | # Load the rails application 2 | require File.expand_path('../application', __FILE__) 3 | 4 | # Initialize the rails application 5 | RailsOpenid::Application.initialize! 6 | -------------------------------------------------------------------------------- /examples/rails_openid/config/environments/development.rb: -------------------------------------------------------------------------------- 1 | RailsOpenid::Application.configure do 2 | # Settings specified here will take precedence over those in config/application.rb 3 | 4 | # In the development environment your application's code is reloaded on 5 | # every request. This slows down response time but is perfect for development 6 | # since you don't have to restart the web server when you make code changes. 7 | config.cache_classes = false 8 | 9 | # Log error messages when you accidentally call methods on nil. 10 | config.whiny_nils = true 11 | 12 | # Show full error reports and disable caching 13 | config.consider_all_requests_local = true 14 | config.action_controller.perform_caching = false 15 | 16 | # Don't care if the mailer can't send 17 | config.action_mailer.raise_delivery_errors = false 18 | 19 | # Print deprecation notices to the Rails logger 20 | config.active_support.deprecation = :log 21 | 22 | # Only use best-standards-support built into browsers 23 | config.action_dispatch.best_standards_support = :builtin 24 | 25 | # Raise exception on mass assignment protection for Active Record models 26 | config.active_record.mass_assignment_sanitizer = :strict 27 | 28 | # Log the query plan for queries taking more than this (works 29 | # with SQLite, MySQL, and PostgreSQL) 30 | config.active_record.auto_explain_threshold_in_seconds = 0.5 31 | 32 | # Do not compress assets 33 | config.assets.compress = false 34 | 35 | # Expands the lines which load the assets 36 | config.assets.debug = true 37 | end 38 | -------------------------------------------------------------------------------- /examples/rails_openid/config/environments/production.rb: -------------------------------------------------------------------------------- 1 | RailsOpenid::Application.configure do 2 | # Settings specified here will take precedence over those in config/application.rb 3 | 4 | # Code is not reloaded between requests 5 | config.cache_classes = true 6 | 7 | # Full error reports are disabled and caching is turned on 8 | config.consider_all_requests_local = false 9 | config.action_controller.perform_caching = true 10 | 11 | # Disable Rails's static asset server (Apache or nginx will already do this) 12 | config.serve_static_assets = false 13 | 14 | # Compress JavaScripts and CSS 15 | config.assets.compress = true 16 | 17 | # Don't fallback to assets pipeline if a precompiled asset is missed 18 | config.assets.compile = false 19 | 20 | # Generate digests for assets URLs 21 | config.assets.digest = true 22 | 23 | # Defaults to nil and saved in location specified by config.assets.prefix 24 | # config.assets.manifest = YOUR_PATH 25 | 26 | # Specifies the header that your server uses for sending files 27 | # config.action_dispatch.x_sendfile_header = "X-Sendfile" # for apache 28 | # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for nginx 29 | 30 | # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies. 31 | # config.force_ssl = true 32 | 33 | # See everything in the log (default is :info) 34 | # config.log_level = :debug 35 | 36 | # Prepend all log lines with the following tags 37 | # config.log_tags = [ :subdomain, :uuid ] 38 | 39 | # Use a different logger for distributed setups 40 | # config.logger = ActiveSupport::TaggedLogging.new(SyslogLogger.new) 41 | 42 | # Use a different cache store in production 43 | # config.cache_store = :mem_cache_store 44 | 45 | # Enable serving of images, stylesheets, and JavaScripts from an asset server 46 | # config.action_controller.asset_host = "http://assets.example.com" 47 | 48 | # Precompile additional assets (application.js, application.css, and all non-JS/CSS are already added) 49 | # config.assets.precompile += %w( search.js ) 50 | 51 | # Disable delivery errors, bad email addresses will be ignored 52 | # config.action_mailer.raise_delivery_errors = false 53 | 54 | # Enable threaded mode 55 | # config.threadsafe! 56 | 57 | # Enable locale fallbacks for I18n (makes lookups for any locale fall back to 58 | # the I18n.default_locale when a translation can not be found) 59 | config.i18n.fallbacks = true 60 | 61 | # Send deprecation notices to registered listeners 62 | config.active_support.deprecation = :notify 63 | 64 | # Log the query plan for queries taking more than this (works 65 | # with SQLite, MySQL, and PostgreSQL) 66 | # config.active_record.auto_explain_threshold_in_seconds = 0.5 67 | end 68 | -------------------------------------------------------------------------------- /examples/rails_openid/config/environments/test.rb: -------------------------------------------------------------------------------- 1 | RailsOpenid::Application.configure do 2 | # Settings specified here will take precedence over those in config/application.rb 3 | 4 | # The test environment is used exclusively to run your application's 5 | # test suite. You never need to work with it otherwise. Remember that 6 | # your test database is "scratch space" for the test suite and is wiped 7 | # and recreated between test runs. Don't rely on the data there! 8 | config.cache_classes = true 9 | 10 | # Configure static asset server for tests with Cache-Control for performance 11 | config.serve_static_assets = true 12 | config.static_cache_control = "public, max-age=3600" 13 | 14 | # Log error messages when you accidentally call methods on nil 15 | config.whiny_nils = true 16 | 17 | # Show full error reports and disable caching 18 | config.consider_all_requests_local = true 19 | config.action_controller.perform_caching = false 20 | 21 | # Raise exceptions instead of rendering exception templates 22 | config.action_dispatch.show_exceptions = false 23 | 24 | # Disable request forgery protection in test environment 25 | config.action_controller.allow_forgery_protection = false 26 | 27 | # Tell Action Mailer not to deliver emails to the real world. 28 | # The :test delivery method accumulates sent emails in the 29 | # ActionMailer::Base.deliveries array. 30 | config.action_mailer.delivery_method = :test 31 | 32 | # Raise exception on mass assignment protection for Active Record models 33 | config.active_record.mass_assignment_sanitizer = :strict 34 | 35 | # Print deprecation notices to the stderr 36 | config.active_support.deprecation = :stderr 37 | end 38 | -------------------------------------------------------------------------------- /examples/rails_openid/config/initializers/backtrace_silencers.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces. 4 | # Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ } 5 | 6 | # You can also remove all the silencers if you're trying to debug a problem that might stem from framework code. 7 | # Rails.backtrace_cleaner.remove_silencers! 8 | -------------------------------------------------------------------------------- /examples/rails_openid/config/initializers/inflections.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Add new inflection rules using the following format 4 | # (all these examples are active by default): 5 | # ActiveSupport::Inflector.inflections do |inflect| 6 | # inflect.plural /^(ox)$/i, '\1en' 7 | # inflect.singular /^(ox)en/i, '\1' 8 | # inflect.irregular 'person', 'people' 9 | # inflect.uncountable %w( fish sheep ) 10 | # end 11 | # 12 | # These inflection rules are supported but not enabled by default: 13 | # ActiveSupport::Inflector.inflections do |inflect| 14 | # inflect.acronym 'RESTful' 15 | # end 16 | -------------------------------------------------------------------------------- /examples/rails_openid/config/initializers/mime_types.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Add new mime types for use in respond_to blocks: 4 | # Mime::Type.register "text/richtext", :rtf 5 | # Mime::Type.register_alias "text/html", :iphone 6 | -------------------------------------------------------------------------------- /examples/rails_openid/config/initializers/rails_root.rb: -------------------------------------------------------------------------------- 1 | ::RAILS_ROOT = Rails.root 2 | -------------------------------------------------------------------------------- /examples/rails_openid/config/initializers/secret_token.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Your secret key for verifying the integrity of signed cookies. 4 | # If you change this key, all old signed cookies will become invalid! 5 | # Make sure the secret is at least 30 characters and all random, 6 | # no regular words or you'll be exposed to dictionary attacks. 7 | RailsOpenid::Application.config.secret_token = '2314c4d00e3702d446505b8df2732c433379a0d61ac94c32a25f71612ab6df457bc9979eb32cae28ad6feacdd5a9ae7ac330934c5fb53877e02ce8e23ac0f494' 8 | -------------------------------------------------------------------------------- /examples/rails_openid/config/initializers/session_store.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | RailsOpenid::Application.config.session_store :cookie_store, :key => '_rails_openid_session' 4 | 5 | # Use the database for sessions instead of the cookie-based default, 6 | # which shouldn't be used to store highly confidential information 7 | # (create the session table with "rails generate session_migration") 8 | # RailsOpenid::Application.config.session_store :active_record_store 9 | -------------------------------------------------------------------------------- /examples/rails_openid/config/initializers/wrap_parameters.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | # 3 | # This file contains settings for ActionController::ParamsWrapper which 4 | # is enabled by default. 5 | 6 | # Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array. 7 | ActiveSupport.on_load(:action_controller) do 8 | wrap_parameters :format => [:json] 9 | end 10 | 11 | # Disable root element in JSON by default. 12 | ActiveSupport.on_load(:active_record) do 13 | self.include_root_in_json = false 14 | end 15 | -------------------------------------------------------------------------------- /examples/rails_openid/config/locales/en.yml: -------------------------------------------------------------------------------- 1 | # Sample localization file for English. Add more files in this directory for other locales. 2 | # See https://github.com/svenfuchs/rails-i18n/tree/master/rails%2Flocale for starting points. 3 | 4 | en: 5 | hello: "Hello world" 6 | -------------------------------------------------------------------------------- /examples/rails_openid/config/routes.rb: -------------------------------------------------------------------------------- 1 | RailsOpenid::Application.routes.draw do 2 | root :controller => 'login', :action => :index 3 | match 'server/xrds', :controller => 'server', :action => 'idp_xrds' 4 | match 'user/:username', :controller => 'server', :action => 'user_page' 5 | match 'user/:username/xrds', :controller => 'server', :action => 'user_xrds' 6 | 7 | # Allow downloading Web Service WSDL as a file with an extension 8 | # instead of a file named 'wsdl' 9 | match ':controller/service.wsdl', :action => 'wsdl' 10 | 11 | # Install the default route as the lowest priority. 12 | match ':controller/:action/:id' 13 | 14 | 15 | # The priority is based upon order of creation: 16 | # first created -> highest priority. 17 | 18 | # Sample of regular route: 19 | # match 'products/:id' => 'catalog#view' 20 | # Keep in mind you can assign values other than :controller and :action 21 | 22 | # Sample of named route: 23 | # match 'products/:id/purchase' => 'catalog#purchase', :as => :purchase 24 | # This route can be invoked with purchase_url(:id => product.id) 25 | 26 | # Sample resource route (maps HTTP verbs to controller actions automatically): 27 | # resources :products 28 | 29 | # Sample resource route with options: 30 | # resources :products do 31 | # member do 32 | # get 'short' 33 | # post 'toggle' 34 | # end 35 | # 36 | # collection do 37 | # get 'sold' 38 | # end 39 | # end 40 | 41 | # Sample resource route with sub-resources: 42 | # resources :products do 43 | # resources :comments, :sales 44 | # resource :seller 45 | # end 46 | 47 | # Sample resource route with more complex sub-resources 48 | # resources :products do 49 | # resources :comments 50 | # resources :sales do 51 | # get 'recent', :on => :collection 52 | # end 53 | # end 54 | 55 | # Sample resource route within a namespace: 56 | # namespace :admin do 57 | # # Directs /admin/products/* to Admin::ProductsController 58 | # # (app/controllers/admin/products_controller.rb) 59 | # resources :products 60 | # end 61 | 62 | # You can have the root of your site routed with "root" 63 | # just remember to delete public/index.html. 64 | # root :to => 'welcome#index' 65 | 66 | # See how all your routes lay out with "rake routes" 67 | 68 | # This is a legacy wild controller route that's not recommended for RESTful applications. 69 | # Note: This route will make all actions in every controller accessible via GET requests. 70 | match ':controller(/:action(/:id))(.:format)' 71 | end 72 | -------------------------------------------------------------------------------- /examples/rails_openid/db/development.sqlite3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openid/ruby-openid/13a88ad6442133a613d2b7d6601991a84b34630d/examples/rails_openid/db/development.sqlite3 -------------------------------------------------------------------------------- /examples/rails_openid/db/seeds.rb: -------------------------------------------------------------------------------- 1 | # This file should contain all the record creation needed to seed the database with its default values. 2 | # The data can then be loaded with the rake db:seed (or created alongside the db with db:setup). 3 | # 4 | # Examples: 5 | # 6 | # cities = City.create([{ :name => 'Chicago' }, { :name => 'Copenhagen' }]) 7 | # Mayor.create(:name => 'Emanuel', :city => cities.first) 8 | -------------------------------------------------------------------------------- /examples/rails_openid/doc/README_FOR_APP: -------------------------------------------------------------------------------- 1 | Use this README file to introduce your application and point to useful places in the API for learning more. 2 | Run "rake doc:app" to generate API documentation for your models, controllers, helpers, and libraries. 3 | -------------------------------------------------------------------------------- /examples/rails_openid/lib/assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openid/ruby-openid/13a88ad6442133a613d2b7d6601991a84b34630d/examples/rails_openid/lib/assets/.gitkeep -------------------------------------------------------------------------------- /examples/rails_openid/lib/tasks/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openid/ruby-openid/13a88ad6442133a613d2b7d6601991a84b34630d/examples/rails_openid/lib/tasks/.gitkeep -------------------------------------------------------------------------------- /examples/rails_openid/log/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openid/ruby-openid/13a88ad6442133a613d2b7d6601991a84b34630d/examples/rails_openid/log/.gitkeep -------------------------------------------------------------------------------- /examples/rails_openid/public/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | The page you were looking for doesn't exist (404) 5 | 17 | 18 | 19 | 20 | 21 |
22 |

The page you were looking for doesn't exist.

23 |

You may have mistyped the address or the page may have moved.

24 |
25 | 26 | 27 | -------------------------------------------------------------------------------- /examples/rails_openid/public/422.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | The change you wanted was rejected (422) 5 | 17 | 18 | 19 | 20 | 21 |
22 |

The change you wanted was rejected.

23 |

Maybe you tried to change something you didn't have access to.

24 |
25 | 26 | 27 | -------------------------------------------------------------------------------- /examples/rails_openid/public/500.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | We're sorry, but something went wrong (500) 5 | 17 | 18 | 19 | 20 | 21 |
22 |

We're sorry, but something went wrong.

23 |
24 | 25 | 26 | -------------------------------------------------------------------------------- /examples/rails_openid/public/dispatch.cgi: -------------------------------------------------------------------------------- 1 | #!/usr/bin/ruby1.8 2 | 3 | #!/usr/local/bin/ruby 4 | 5 | require File.dirname(__FILE__) + "/../config/environment" unless defined?(RAILS_ROOT) 6 | 7 | # If you're using RubyGems and mod_ruby, this require should be changed to an absolute path one, like: 8 | # "/usr/local/lib/ruby/gems/1.8/gems/rails-0.8.0/lib/dispatcher" -- otherwise performance is severely impaired 9 | require "dispatcher" 10 | 11 | ADDITIONAL_LOAD_PATHS.reverse.each { |dir| $:.unshift(dir) if File.directory?(dir) } if defined?(Apache::RubyRun) 12 | Dispatcher.dispatch -------------------------------------------------------------------------------- /examples/rails_openid/public/dispatch.fcgi: -------------------------------------------------------------------------------- 1 | #!/usr/bin/ruby1.8 2 | 3 | #!/usr/local/bin/ruby 4 | # 5 | # You may specify the path to the FastCGI crash log (a log of unhandled 6 | # exceptions which forced the FastCGI instance to exit, great for debugging) 7 | # and the number of requests to process before running garbage collection. 8 | # 9 | # By default, the FastCGI crash log is RAILS_ROOT/log/fastcgi.crash.log 10 | # and the GC period is nil (turned off). A reasonable number of requests 11 | # could range from 10-100 depending on the memory footprint of your app. 12 | # 13 | # Example: 14 | # # Default log path, normal GC behavior. 15 | # RailsFCGIHandler.process! 16 | # 17 | # # Default log path, 50 requests between GC. 18 | # RailsFCGIHandler.process! nil, 50 19 | # 20 | # # Custom log path, normal GC behavior. 21 | # RailsFCGIHandler.process! '/var/log/myapp_fcgi_crash.log' 22 | # 23 | require File.dirname(__FILE__) + "/../config/environment" 24 | require 'fcgi_handler' 25 | 26 | RailsFCGIHandler.process! 27 | -------------------------------------------------------------------------------- /examples/rails_openid/public/dispatch.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/ruby1.8 2 | 3 | #!/usr/local/bin/ruby 4 | 5 | require File.dirname(__FILE__) + "/../config/environment" unless defined?(RAILS_ROOT) 6 | 7 | # If you're using RubyGems and mod_ruby, this require should be changed to an absolute path one, like: 8 | # "/usr/local/lib/ruby/gems/1.8/gems/rails-0.8.0/lib/dispatcher" -- otherwise performance is severely impaired 9 | require "dispatcher" 10 | 11 | ADDITIONAL_LOAD_PATHS.reverse.each { |dir| $:.unshift(dir) if File.directory?(dir) } if defined?(Apache::RubyRun) 12 | Dispatcher.dispatch -------------------------------------------------------------------------------- /examples/rails_openid/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openid/ruby-openid/13a88ad6442133a613d2b7d6601991a84b34630d/examples/rails_openid/public/favicon.ico -------------------------------------------------------------------------------- /examples/rails_openid/public/images/openid_login_bg.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openid/ruby-openid/13a88ad6442133a613d2b7d6601991a84b34630d/examples/rails_openid/public/images/openid_login_bg.gif -------------------------------------------------------------------------------- /examples/rails_openid/public/javascripts/application.js: -------------------------------------------------------------------------------- 1 | // Place your application-specific JavaScript functions and classes here 2 | // This file is automatically included by javascript_include_tag :defaults 3 | -------------------------------------------------------------------------------- /examples/rails_openid/public/robots.txt: -------------------------------------------------------------------------------- 1 | # See http://www.robotstxt.org/wc/norobots.html for documentation on how to use the robots.txt file 2 | # 3 | # To ban all spiders from the entire site uncomment the next two lines: 4 | # User-Agent: * 5 | # Disallow: / 6 | -------------------------------------------------------------------------------- /examples/rails_openid/script/rails: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # This command will automatically be run when you run "rails" with Rails 3 gems installed from the root of your application. 3 | 4 | APP_PATH = File.expand_path('../../config/application', __FILE__) 5 | require File.expand_path('../../config/boot', __FILE__) 6 | require 'rails/commands' 7 | -------------------------------------------------------------------------------- /examples/rails_openid/test/fixtures/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openid/ruby-openid/13a88ad6442133a613d2b7d6601991a84b34630d/examples/rails_openid/test/fixtures/.gitkeep -------------------------------------------------------------------------------- /examples/rails_openid/test/functional/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openid/ruby-openid/13a88ad6442133a613d2b7d6601991a84b34630d/examples/rails_openid/test/functional/.gitkeep -------------------------------------------------------------------------------- /examples/rails_openid/test/functional/login_controller_test.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + '/../test_helper' 2 | require 'login_controller' 3 | 4 | # Re-raise errors caught by the controller. 5 | class LoginController; def rescue_action(e) raise e end; end 6 | 7 | class LoginControllerTest < Test::Unit::TestCase 8 | def setup 9 | @controller = LoginController.new 10 | @request = ActionController::TestRequest.new 11 | @response = ActionController::TestResponse.new 12 | end 13 | 14 | # Replace this with your real tests. 15 | def test_truth 16 | assert true 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /examples/rails_openid/test/functional/server_controller_test.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + '/../test_helper' 2 | require 'server_controller' 3 | 4 | # Re-raise errors caught by the controller. 5 | class ServerController; def rescue_action(e) raise e end; end 6 | 7 | class ServerControllerTest < Test::Unit::TestCase 8 | def setup 9 | @controller = ServerController.new 10 | @request = ActionController::TestRequest.new 11 | @response = ActionController::TestResponse.new 12 | end 13 | 14 | # Replace this with your real tests. 15 | def test_truth 16 | assert true 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /examples/rails_openid/test/integration/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openid/ruby-openid/13a88ad6442133a613d2b7d6601991a84b34630d/examples/rails_openid/test/integration/.gitkeep -------------------------------------------------------------------------------- /examples/rails_openid/test/performance/browsing_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | require 'rails/performance_test_help' 3 | 4 | class BrowsingTest < ActionDispatch::PerformanceTest 5 | # Refer to the documentation for all available options 6 | # self.profile_options = { :runs => 5, :metrics => [:wall_time, :memory] 7 | # :output => 'tmp/performance', :formats => [:flat] } 8 | 9 | def test_homepage 10 | get '/' 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /examples/rails_openid/test/test_helper.rb: -------------------------------------------------------------------------------- 1 | ENV["RAILS_ENV"] = "test" 2 | require File.expand_path('../../config/environment', __FILE__) 3 | require 'rails/test_help' 4 | 5 | class ActiveSupport::TestCase 6 | # Setup all fixtures in test/fixtures/*.(yml|csv) for all tests in alphabetical order. 7 | # 8 | # Note: You'll currently still have to declare fixtures explicitly in integration tests 9 | # -- they do not yet inherit this setting 10 | fixtures :all 11 | 12 | # Add more helper methods to be used by all tests here... 13 | end 14 | -------------------------------------------------------------------------------- /examples/rails_openid/test/unit/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openid/ruby-openid/13a88ad6442133a613d2b7d6601991a84b34630d/examples/rails_openid/test/unit/.gitkeep -------------------------------------------------------------------------------- /lib/hmac/hmac.rb: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2001 Daiki Ueno 2 | # This library is distributed under the terms of the Ruby license. 3 | 4 | # This module provides common interface to HMAC engines. 5 | # HMAC standard is documented in RFC 2104: 6 | # 7 | # H. Krawczyk et al., "HMAC: Keyed-Hashing for Message Authentication", 8 | # RFC 2104, February 1997 9 | # 10 | # These APIs are inspired by JCE 1.2's javax.crypto.Mac interface. 11 | # 12 | # 13 | 14 | module HMAC 15 | class Base 16 | def initialize(algorithm, block_size, output_length, key) 17 | @algorithm = algorithm 18 | @block_size = block_size 19 | @output_length = output_length 20 | @status = STATUS_UNDEFINED 21 | @key_xor_ipad = '' 22 | @key_xor_opad = '' 23 | set_key(key) unless key.nil? 24 | end 25 | 26 | private 27 | def check_status 28 | unless @status == STATUS_INITIALIZED 29 | raise RuntimeError, 30 | "The underlying hash algorithm has not yet been initialized." 31 | end 32 | end 33 | 34 | public 35 | def set_key(key) 36 | # If key is longer than the block size, apply hash function 37 | # to key and use the result as a real key. 38 | key = @algorithm.digest(key) if key.size > @block_size 39 | key_xor_ipad = "\x36" * @block_size 40 | key_xor_opad = "\x5C" * @block_size 41 | for i in 0 .. key.size - 1 42 | key_xor_ipad[i] ^= key[i] 43 | key_xor_opad[i] ^= key[i] 44 | end 45 | @key_xor_ipad = key_xor_ipad 46 | @key_xor_opad = key_xor_opad 47 | @md = @algorithm.new 48 | @status = STATUS_INITIALIZED 49 | end 50 | 51 | def reset_key 52 | @key_xor_ipad.gsub!(/./, '?') 53 | @key_xor_opad.gsub!(/./, '?') 54 | @key_xor_ipad[0..-1] = '' 55 | @key_xor_opad[0..-1] = '' 56 | @status = STATUS_UNDEFINED 57 | end 58 | 59 | def update(text) 60 | check_status 61 | # perform inner H 62 | md = @algorithm.new 63 | md.update(@key_xor_ipad) 64 | md.update(text) 65 | str = md.digest 66 | # perform outer H 67 | md = @algorithm.new 68 | md.update(@key_xor_opad) 69 | md.update(str) 70 | @md = md 71 | end 72 | alias << update 73 | 74 | def digest 75 | check_status 76 | @md.digest 77 | end 78 | 79 | def hexdigest 80 | check_status 81 | @md.hexdigest 82 | end 83 | alias to_s hexdigest 84 | 85 | # These two class methods below are safer than using above 86 | # instance methods combinatorially because an instance will have 87 | # held a key even if it's no longer in use. 88 | def Base.digest(key, text) 89 | begin 90 | hmac = self.new(key) 91 | hmac.update(text) 92 | hmac.digest 93 | ensure 94 | hmac.reset_key 95 | end 96 | end 97 | 98 | def Base.hexdigest(key, text) 99 | begin 100 | hmac = self.new(key) 101 | hmac.update(text) 102 | hmac.hexdigest 103 | ensure 104 | hmac.reset_key 105 | end 106 | end 107 | 108 | private_class_method :new, :digest, :hexdigest 109 | end 110 | 111 | STATUS_UNDEFINED, STATUS_INITIALIZED = 0, 1 112 | end 113 | -------------------------------------------------------------------------------- /lib/hmac/sha1.rb: -------------------------------------------------------------------------------- 1 | require 'hmac/hmac' 2 | require 'digest/sha1' 3 | 4 | module HMAC 5 | class SHA1 < Base 6 | def initialize(key = nil) 7 | super(Digest::SHA1, 64, 20, key) 8 | end 9 | public_class_method :new, :digest, :hexdigest 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /lib/hmac/sha2.rb: -------------------------------------------------------------------------------- 1 | require 'hmac/hmac' 2 | require 'digest/sha2' 3 | 4 | module HMAC 5 | class SHA256 < Base 6 | def initialize(key = nil) 7 | super(Digest::SHA256, 64, 32, key) 8 | end 9 | public_class_method :new, :digest, :hexdigest 10 | end 11 | 12 | class SHA384 < Base 13 | def initialize(key = nil) 14 | super(Digest::SHA384, 128, 48, key) 15 | end 16 | public_class_method :new, :digest, :hexdigest 17 | end 18 | 19 | class SHA512 < Base 20 | def initialize(key = nil) 21 | super(Digest::SHA512, 128, 64, key) 22 | end 23 | public_class_method :new, :digest, :hexdigest 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /lib/openid.rb: -------------------------------------------------------------------------------- 1 | # Copyright 2006-2007 JanRain, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); you 4 | # may not use this file except in compliance with the License. You may 5 | # obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 12 | # implied. See the License for the specific language governing 13 | # permissions and limitations under the License. 14 | 15 | module OpenID 16 | end 17 | 18 | require 'openid/version' 19 | require 'openid/consumer' 20 | require 'openid/server' 21 | -------------------------------------------------------------------------------- /lib/openid/consumer/discovery_manager.rb: -------------------------------------------------------------------------------- 1 | module OpenID 2 | class Consumer 3 | 4 | # A set of discovered services, for tracking which providers have 5 | # been attempted for an OpenID identifier 6 | class DiscoveredServices 7 | attr_reader :current 8 | 9 | def initialize(starting_url, yadis_url, services) 10 | @starting_url = starting_url 11 | @yadis_url = yadis_url 12 | @services = services.dup 13 | @current = nil 14 | end 15 | 16 | def next 17 | @current = @services.shift 18 | end 19 | 20 | def for_url?(url) 21 | [@starting_url, @yadis_url].member?(url) 22 | end 23 | 24 | def started? 25 | !@current.nil? 26 | end 27 | 28 | def empty? 29 | @services.empty? 30 | end 31 | 32 | def to_session_value 33 | services = @services.map{|s| s.respond_to?(:to_session_value) ? s.to_session_value : s } 34 | current_val = @current.respond_to?(:to_session_value) ? @current.to_session_value : @current 35 | 36 | { 37 | 'starting_url' => @starting_url, 38 | 'yadis_url' => @yadis_url, 39 | 'services' => services, 40 | 'current' => current_val 41 | } 42 | end 43 | 44 | def ==(other) 45 | to_session_value == other.to_session_value 46 | end 47 | 48 | def self.from_session_value(value) 49 | return value unless value.is_a?(Hash) 50 | 51 | services = value['services'].map{|s| OpenID::OpenIDServiceEndpoint.from_session_value(s) } 52 | current = OpenID::OpenIDServiceEndpoint.from_session_value(value['current']) 53 | 54 | obj = self.new(value['starting_url'], value['yadis_url'], services) 55 | obj.instance_variable_set("@current", current) 56 | obj 57 | end 58 | end 59 | 60 | # Manages calling discovery and tracking which endpoints have 61 | # already been attempted. 62 | class DiscoveryManager 63 | def initialize(session, url, session_key_suffix=nil) 64 | @url = url 65 | 66 | @session = OpenID::Consumer::Session.new(session, DiscoveredServices) 67 | @session_key_suffix = session_key_suffix || 'auth' 68 | end 69 | 70 | def get_next_service 71 | manager = get_manager 72 | if !manager.nil? && manager.empty? 73 | destroy_manager 74 | manager = nil 75 | end 76 | 77 | if manager.nil? 78 | yadis_url, services = yield @url 79 | manager = create_manager(yadis_url, services) 80 | end 81 | 82 | if !manager.nil? 83 | service = manager.next 84 | store(manager) 85 | else 86 | service = nil 87 | end 88 | 89 | return service 90 | end 91 | 92 | def cleanup(force=false) 93 | manager = get_manager(force) 94 | if !manager.nil? 95 | service = manager.current 96 | destroy_manager(force) 97 | else 98 | service = nil 99 | end 100 | return service 101 | end 102 | 103 | protected 104 | 105 | def get_manager(force=false) 106 | manager = load 107 | if force || manager.nil? || manager.for_url?(@url) 108 | return manager 109 | else 110 | return nil 111 | end 112 | end 113 | 114 | def create_manager(yadis_url, services) 115 | manager = get_manager 116 | if !manager.nil? 117 | raise StandardError, "There is already a manager for #{yadis_url}" 118 | end 119 | if services.empty? 120 | return nil 121 | end 122 | manager = DiscoveredServices.new(@url, yadis_url, services) 123 | store(manager) 124 | return manager 125 | end 126 | 127 | def destroy_manager(force=false) 128 | if !get_manager(force).nil? 129 | destroy! 130 | end 131 | end 132 | 133 | def session_key 134 | 'OpenID::Consumer::DiscoveredServices::' + @session_key_suffix 135 | end 136 | 137 | def store(manager) 138 | @session[session_key] = manager 139 | end 140 | 141 | def load 142 | @session[session_key] 143 | end 144 | 145 | def destroy! 146 | @session[session_key] = nil 147 | end 148 | end 149 | end 150 | end 151 | -------------------------------------------------------------------------------- /lib/openid/consumer/html_parse.rb: -------------------------------------------------------------------------------- 1 | require "openid/yadis/htmltokenizer" 2 | 3 | module OpenID 4 | 5 | # Stuff to remove before we start looking for tags 6 | REMOVED_RE = / 7 | # Comments 8 | 9 | 10 | # CDATA blocks 11 | | 12 | 13 | # script blocks 14 | | ]*>.*?<\/script> 20 | 21 | /mix 22 | 23 | def OpenID.openid_unescape(s) 24 | s.gsub('&','&').gsub('<','<').gsub('>','>').gsub('"','"') 25 | end 26 | 27 | def OpenID.unescape_hash(h) 28 | newh = {} 29 | h.map{|k,v| 30 | newh[k]=openid_unescape(v) 31 | } 32 | newh 33 | end 34 | 35 | 36 | def OpenID.parse_link_attrs(html) 37 | begin 38 | stripped = html.gsub(REMOVED_RE,'') 39 | rescue ArgumentError 40 | begin 41 | stripped = html.encode('UTF-8', 'binary', :invalid => :replace, :undef => :replace, :replace => '').gsub(REMOVED_RE,'') 42 | rescue Encoding::UndefinedConversionError, Encoding::ConverterNotFoundError 43 | # needed for a problem in JRuby where it can't handle the conversion. 44 | # see details here: https://github.com/jruby/jruby/issues/829 45 | stripped = html.encode('UTF-8', 'ASCII', :invalid => :replace, :undef => :replace, :replace => '').gsub(REMOVED_RE,'') 46 | end 47 | end 48 | parser = HTMLTokenizer.new(stripped) 49 | 50 | links = [] 51 | # to keep track of whether or not we are in the head element 52 | in_head = false 53 | in_html = false 54 | saw_head = false 55 | 56 | begin 57 | while el = parser.getTag('head', '/head', 'link', 'body', '/body', 58 | 'html', '/html') 59 | 60 | # we are leaving head or have reached body, so we bail 61 | return links if ['/head', 'body', '/body', '/html'].member?(el.tag_name) 62 | 63 | # enforce html > head > link 64 | if el.tag_name == 'html' 65 | in_html = true 66 | end 67 | next unless in_html 68 | if el.tag_name == 'head' 69 | if saw_head 70 | return links #only allow one head 71 | end 72 | saw_head = true 73 | unless el.to_s[-2] == 47 # tag ends with a /: a short tag 74 | in_head = true 75 | end 76 | end 77 | next unless in_head 78 | 79 | return links if el.tag_name == 'html' 80 | 81 | if el.tag_name == 'link' 82 | links << unescape_hash(el.attr_hash) 83 | end 84 | 85 | end 86 | rescue Exception # just stop parsing if there's an error 87 | end 88 | return links 89 | end 90 | 91 | def OpenID.rel_matches(rel_attr, target_rel) 92 | # Does this target_rel appear in the rel_str? 93 | # XXX: TESTME 94 | rels = rel_attr.strip().split() 95 | rels.each { |rel| 96 | rel = rel.downcase 97 | if rel == target_rel 98 | return true 99 | end 100 | } 101 | 102 | return false 103 | end 104 | 105 | def OpenID.link_has_rel(link_attrs, target_rel) 106 | # Does this link have target_rel as a relationship? 107 | 108 | # XXX: TESTME 109 | rel_attr = link_attrs['rel'] 110 | return (rel_attr and rel_matches(rel_attr, target_rel)) 111 | end 112 | 113 | def OpenID.find_links_rel(link_attrs_list, target_rel) 114 | # Filter the list of link attributes on whether it has target_rel 115 | # as a relationship. 116 | 117 | # XXX: TESTME 118 | matchesTarget = lambda { |attrs| link_has_rel(attrs, target_rel) } 119 | result = [] 120 | 121 | link_attrs_list.each { |item| 122 | if matchesTarget.call(item) 123 | result << item 124 | end 125 | } 126 | 127 | return result 128 | end 129 | 130 | def OpenID.find_first_href(link_attrs_list, target_rel) 131 | # Return the value of the href attribute for the first link tag in 132 | # the list that has target_rel as a relationship. 133 | 134 | # XXX: TESTME 135 | matches = find_links_rel(link_attrs_list, target_rel) 136 | if !matches or matches.empty? 137 | return nil 138 | end 139 | 140 | first = matches[0] 141 | return first['href'] 142 | end 143 | end 144 | 145 | -------------------------------------------------------------------------------- /lib/openid/consumer/session.rb: -------------------------------------------------------------------------------- 1 | module OpenID 2 | class Consumer 3 | class Session 4 | def initialize(session, decode_klass = nil) 5 | @session = session 6 | @decode_klass = decode_klass 7 | end 8 | 9 | def [](key) 10 | val = @session[key] 11 | @decode_klass ? @decode_klass.from_session_value(val) : val 12 | end 13 | 14 | def []=(key, val) 15 | @session[key] = to_session_value(val) 16 | end 17 | 18 | def keys 19 | @session.keys 20 | end 21 | 22 | private 23 | 24 | def to_session_value(val) 25 | case val 26 | when Array 27 | val.map{|ele| to_session_value(ele) } 28 | when Hash 29 | Hash[*(val.map{|k,v| [k, to_session_value(v)] }.flatten(1))] 30 | else 31 | val.respond_to?(:to_session_value) ? val.to_session_value : val 32 | end 33 | end 34 | end 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /lib/openid/cryptutil.rb: -------------------------------------------------------------------------------- 1 | require "openid/util" 2 | require "digest/sha1" 3 | require "digest/sha2" 4 | begin 5 | require "openssl" 6 | rescue LoadError 7 | begin 8 | # Try loading the ruby-hmac files if they exist 9 | require "hmac-sha1" 10 | require "hmac-sha2" 11 | rescue LoadError 12 | # Nothing exists use included hmac files 13 | require "hmac/sha1" 14 | require "hmac/sha2" 15 | end 16 | end 17 | 18 | module OpenID 19 | # This module contains everything needed to perform low-level 20 | # cryptograph and data manipulation tasks. 21 | module CryptUtil 22 | 23 | # Generate a random number, doing a little extra work to make it 24 | # more likely that it's suitable for cryptography. If your system 25 | # doesn't have /dev/urandom then this number is not 26 | # cryptographically safe. See 27 | # 28 | # for more information. max is the largest possible value of such 29 | # a random number, where the result will be less than max. 30 | def CryptUtil.rand(max) 31 | Kernel.srand() 32 | return Kernel.rand(max) 33 | end 34 | 35 | def CryptUtil.sha1(text) 36 | return Digest::SHA1.digest(text) 37 | end 38 | 39 | def CryptUtil.hmac_sha1(key, text) 40 | if defined? OpenSSL 41 | OpenSSL::HMAC.digest(OpenSSL::Digest::SHA1.new, key, text) 42 | else 43 | return HMAC::SHA1.digest(key, text) 44 | end 45 | end 46 | 47 | def CryptUtil.sha256(text) 48 | return Digest::SHA256.digest(text) 49 | end 50 | 51 | def CryptUtil.hmac_sha256(key, text) 52 | if defined? OpenSSL 53 | OpenSSL::HMAC.digest(OpenSSL::Digest::SHA256.new, key, text) 54 | else 55 | return HMAC::SHA256.digest(key, text) 56 | end 57 | end 58 | 59 | # Generate a random string of the given length, composed of the 60 | # specified characters. If chars is nil, generate a string 61 | # composed of characters in the range 0..255. 62 | def CryptUtil.random_string(length, chars=nil) 63 | s = "" 64 | 65 | unless chars.nil? 66 | length.times { s << chars[rand(chars.length)] } 67 | else 68 | length.times { s << rand(256).chr } 69 | end 70 | return s 71 | end 72 | 73 | # Convert a number to its binary representation; return a string 74 | # of bytes. 75 | def CryptUtil.num_to_binary(n) 76 | bits = n.to_s(2) 77 | prepend = (8 - bits.length % 8) 78 | bits = ('0' * prepend) + bits 79 | return [bits].pack('B*') 80 | end 81 | 82 | # Convert a string of bytes into a number. 83 | def CryptUtil.binary_to_num(s) 84 | # taken from openid-ruby 0.0.1 85 | s = "\000" * (4 - (s.length % 4)) + s 86 | num = 0 87 | s.unpack('N*').each do |x| 88 | num <<= 32 89 | num |= x 90 | end 91 | return num 92 | end 93 | 94 | # Encode a number as a base64-encoded byte string. 95 | def CryptUtil.num_to_base64(l) 96 | return OpenID::Util.to_base64(num_to_binary(l)) 97 | end 98 | 99 | # Decode a base64 byte string to a number. 100 | def CryptUtil.base64_to_num(s) 101 | return binary_to_num(OpenID::Util.from_base64(s)) 102 | end 103 | 104 | def CryptUtil.const_eq(s1, s2) 105 | if s1.length != s2.length 106 | return false 107 | end 108 | result = true 109 | s1.length.times do |i| 110 | result &= (s1[i] == s2[i]) 111 | end 112 | return result 113 | end 114 | end 115 | end 116 | -------------------------------------------------------------------------------- /lib/openid/dh.rb: -------------------------------------------------------------------------------- 1 | require "openid/util" 2 | require "openid/cryptutil" 3 | 4 | module OpenID 5 | 6 | # Encapsulates a Diffie-Hellman key exchange. This class is used 7 | # internally by both the consumer and server objects. 8 | # 9 | # Read more about Diffie-Hellman on wikipedia: 10 | # http://en.wikipedia.org/wiki/Diffie-Hellman 11 | 12 | class DiffieHellman 13 | 14 | # From the OpenID specification 15 | @@default_mod = 155172898181473697471232257763715539915724801966915404479707795314057629378541917580651227423698188993727816152646631438561595825688188889951272158842675419950341258706556549803580104870537681476726513255747040765857479291291572334510643245094715007229621094194349783925984760375594985848253359305585439638443 16 | @@default_gen = 2 17 | 18 | attr_reader :modulus, :generator, :public 19 | 20 | # A new DiffieHellman object, using the modulus and generator from 21 | # the OpenID specification 22 | def DiffieHellman.from_defaults 23 | DiffieHellman.new(@@default_mod, @@default_gen) 24 | end 25 | 26 | def initialize(modulus=nil, generator=nil, priv=nil) 27 | @modulus = modulus.nil? ? @@default_mod : modulus 28 | @generator = generator.nil? ? @@default_gen : generator 29 | set_private(priv.nil? ? OpenID::CryptUtil.rand(@modulus-2) + 1 : priv) 30 | end 31 | 32 | def get_shared_secret(composite) 33 | DiffieHellman.powermod(composite, @private, @modulus) 34 | end 35 | 36 | def xor_secret(algorithm, composite, secret) 37 | dh_shared = get_shared_secret(composite) 38 | packed_dh_shared = OpenID::CryptUtil.num_to_binary(dh_shared) 39 | hashed_dh_shared = algorithm.call(packed_dh_shared) 40 | return DiffieHellman.strxor(secret, hashed_dh_shared) 41 | end 42 | 43 | def using_default_values? 44 | @generator == @@default_gen && @modulus == @@default_mod 45 | end 46 | 47 | private 48 | def set_private(priv) 49 | @private = priv 50 | @public = DiffieHellman.powermod(@generator, @private, @modulus) 51 | end 52 | 53 | def DiffieHellman.strxor(s, t) 54 | if s.length != t.length 55 | raise ArgumentError, "strxor: lengths don't match. " + 56 | "Inputs were #{s.inspect} and #{t.inspect}" 57 | end 58 | 59 | if String.method_defined? :bytes 60 | s.bytes.to_a.zip(t.bytes.to_a).map{|sb,tb| sb^tb}.pack('C*') 61 | else 62 | indices = 0...(s.length) 63 | chrs = indices.collect {|i| (s[i]^t[i]).chr} 64 | chrs.join("") 65 | end 66 | end 67 | 68 | # This code is taken from this post: 69 | # 70 | # by Eric Lee Green. 71 | def DiffieHellman.powermod(x, n, q) 72 | counter=0 73 | n_p=n 74 | y_p=1 75 | z_p=x 76 | while n_p != 0 77 | if n_p[0]==1 78 | y_p=(y_p*z_p) % q 79 | end 80 | n_p = n_p >> 1 81 | z_p = (z_p * z_p) % q 82 | counter += 1 83 | end 84 | return y_p 85 | end 86 | 87 | end 88 | 89 | end 90 | -------------------------------------------------------------------------------- /lib/openid/extension.rb: -------------------------------------------------------------------------------- 1 | require 'openid/message' 2 | 3 | module OpenID 4 | # An interface for OpenID extensions. 5 | class Extension < Object 6 | 7 | def initialize 8 | @ns_uri = nil 9 | @ns_alias = nil 10 | end 11 | 12 | # Get the string arguments that should be added to an OpenID 13 | # message for this extension. 14 | def get_extension_args 15 | raise NotImplementedError 16 | end 17 | 18 | # Add the arguments from this extension to the provided 19 | # message, or create a new message containing only those 20 | # arguments. Returns the message with added extension args. 21 | def to_message(message = nil) 22 | if message.nil? 23 | # warnings.warn('Passing None to Extension.toMessage is deprecated. ' 24 | # 'Creating a message assuming you want OpenID 2.', 25 | # DeprecationWarning, stacklevel=2) 26 | Message.new(OPENID2_NS) 27 | end 28 | message = Message.new if message.nil? 29 | 30 | implicit = message.is_openid1() 31 | 32 | message.namespaces.add_alias(@ns_uri, @ns_alias, implicit) 33 | # XXX python ignores keyerror if m.ns.getAlias(uri) == alias 34 | 35 | message.update_args(@ns_uri, get_extension_args) 36 | return message 37 | end 38 | end 39 | end 40 | -------------------------------------------------------------------------------- /lib/openid/extensions/oauth.rb: -------------------------------------------------------------------------------- 1 | # An implementation of the OpenID OAuth Extension 2 | # Extension 1.0 3 | # see: http://openid.net/specs/ 4 | 5 | require 'openid/extension' 6 | 7 | module OpenID 8 | 9 | module OAuth 10 | NS_URI = "http://specs.openid.net/extensions/oauth/1.0" 11 | # An OAuth token request, sent from a relying 12 | # party to a provider 13 | class Request < Extension 14 | attr_accessor :consumer, :scope, :ns_alias, :ns_uri 15 | def initialize(consumer=nil, scope=nil) 16 | @ns_alias = 'oauth' 17 | @ns_uri = NS_URI 18 | @consumer = consumer 19 | @scope = scope 20 | end 21 | 22 | 23 | def get_extension_args 24 | ns_args = {} 25 | ns_args['consumer'] = @consumer if @consumer 26 | ns_args['scope'] = @scope if @scope 27 | return ns_args 28 | end 29 | 30 | # Instantiate a Request object from the arguments in a 31 | # checkid_* OpenID message 32 | # return nil if the extension was not requested. 33 | def self.from_openid_request(oid_req) 34 | oauth_req = new 35 | args = oid_req.message.get_args(NS_URI) 36 | if args == {} 37 | return nil 38 | end 39 | oauth_req.parse_extension_args(args) 40 | return oauth_req 41 | end 42 | 43 | # Set the state of this request to be that expressed in these 44 | # OAuth arguments 45 | def parse_extension_args(args) 46 | @consumer = args["consumer"] 47 | @scope = args["scope"] 48 | end 49 | 50 | end 51 | 52 | # A OAuth request token response, sent from a provider 53 | # to a relying party 54 | class Response < Extension 55 | attr_accessor :request_token, :scope 56 | def initialize(request_token=nil, scope=nil) 57 | @ns_alias = 'oauth' 58 | @ns_uri = NS_URI 59 | @request_token = request_token 60 | @scope = scope 61 | end 62 | 63 | # Create a Response object from an OpenID::Consumer::SuccessResponse 64 | def self.from_success_response(success_response) 65 | args = success_response.get_signed_ns(NS_URI) 66 | return nil if args.nil? 67 | oauth_resp = new 68 | oauth_resp.parse_extension_args(args) 69 | return oauth_resp 70 | end 71 | 72 | # parse the oauth request arguments into the 73 | # internal state of this object 74 | # if strict is specified, raise an exception when bad data is 75 | # encountered 76 | def parse_extension_args(args, strict=false) 77 | @request_token = args["request_token"] 78 | @scope = args["scope"] 79 | end 80 | 81 | def get_extension_args 82 | ns_args = {} 83 | ns_args['request_token'] = @request_token if @request_token 84 | ns_args['scope'] = @scope if @scope 85 | return ns_args 86 | end 87 | 88 | end 89 | end 90 | 91 | end 92 | -------------------------------------------------------------------------------- /lib/openid/extensions/ui.rb: -------------------------------------------------------------------------------- 1 | # An implementation of the OpenID User Interface Extension 1.0 - DRAFT 0.5 2 | # see: http://svn.openid.net/repos/specifications/user_interface/1.0/trunk/openid-user-interface-extension-1_0.html 3 | 4 | require 'openid/extension' 5 | 6 | module OpenID 7 | 8 | module UI 9 | NS_URI = "http://specs.openid.net/extensions/ui/1.0" 10 | 11 | class Request < Extension 12 | attr_accessor :lang, :icon, :mode, :ns_alias, :ns_uri 13 | def initialize(mode = nil, icon = nil, lang = nil) 14 | @ns_alias = 'ui' 15 | @ns_uri = NS_URI 16 | @lang = lang 17 | @icon = icon 18 | @mode = mode 19 | end 20 | 21 | def get_extension_args 22 | ns_args = {} 23 | ns_args['lang'] = @lang if @lang 24 | ns_args['icon'] = @icon if @icon 25 | ns_args['mode'] = @mode if @mode 26 | return ns_args 27 | end 28 | 29 | # Instantiate a Request object from the arguments in a 30 | # checkid_* OpenID message 31 | # return nil if the extension was not requested. 32 | def self.from_openid_request(oid_req) 33 | ui_req = new 34 | args = oid_req.message.get_args(NS_URI) 35 | if args == {} 36 | return nil 37 | end 38 | ui_req.parse_extension_args(args) 39 | return ui_req 40 | end 41 | 42 | # Set UI extension parameters 43 | def parse_extension_args(args) 44 | @lang = args["lang"] 45 | @icon = args["icon"] 46 | @mode = args["mode"] 47 | end 48 | 49 | end 50 | 51 | end 52 | 53 | end 54 | -------------------------------------------------------------------------------- /lib/openid/kvform.rb: -------------------------------------------------------------------------------- 1 | 2 | module OpenID 3 | 4 | class KVFormError < Exception 5 | end 6 | 7 | module Util 8 | 9 | def Util.seq_to_kv(seq, strict=false) 10 | # Represent a sequence of pairs of strings as newline-terminated 11 | # key:value pairs. The pairs are generated in the order given. 12 | # 13 | # @param seq: The pairs 14 | # 15 | # returns a string representation of the sequence 16 | err = lambda { |msg| 17 | msg = "seq_to_kv warning: #{msg}: #{seq.inspect}" 18 | if strict 19 | raise KVFormError, msg 20 | else 21 | Util.log(msg) 22 | end 23 | } 24 | 25 | lines = [] 26 | seq.each { |k, v| 27 | if !k.is_a?(String) 28 | err.call("Converting key to string: #{k.inspect}") 29 | k = k.to_s 30 | end 31 | 32 | if !k.index("\n").nil? 33 | raise KVFormError, "Invalid input for seq_to_kv: key contains newline: #{k.inspect}" 34 | end 35 | 36 | if !k.index(":").nil? 37 | raise KVFormError, "Invalid input for seq_to_kv: key contains colon: #{k.inspect}" 38 | end 39 | 40 | if k.strip() != k 41 | err.call("Key has whitespace at beginning or end: #{k.inspect}") 42 | end 43 | 44 | if !v.is_a?(String) 45 | err.call("Converting value to string: #{v.inspect}") 46 | v = v.to_s 47 | end 48 | 49 | if !v.index("\n").nil? 50 | raise KVFormError, "Invalid input for seq_to_kv: value contains newline: #{v.inspect}" 51 | end 52 | 53 | if v.strip() != v 54 | err.call("Value has whitespace at beginning or end: #{v.inspect}") 55 | end 56 | 57 | lines << k + ":" + v + "\n" 58 | } 59 | 60 | return lines.join("") 61 | end 62 | 63 | def Util.kv_to_seq(data, strict=false) 64 | # After one parse, seq_to_kv and kv_to_seq are inverses, with no 65 | # warnings: 66 | # 67 | # seq = kv_to_seq(s) 68 | # seq_to_kv(kv_to_seq(seq)) == seq 69 | err = lambda { |msg| 70 | msg = "kv_to_seq warning: #{msg}: #{data.inspect}" 71 | if strict 72 | raise KVFormError, msg 73 | else 74 | Util.log(msg) 75 | end 76 | } 77 | 78 | lines = data.split("\n") 79 | if data.length == 0 80 | return [] 81 | end 82 | 83 | if data[-1].chr != "\n" 84 | err.call("Does not end in a newline") 85 | # We don't expect the last element of lines to be an empty 86 | # string because split() doesn't behave that way. 87 | end 88 | 89 | pairs = [] 90 | line_num = 0 91 | lines.each { |line| 92 | line_num += 1 93 | 94 | # Ignore blank lines 95 | if line.strip() == "" 96 | next 97 | end 98 | 99 | pair = line.split(':', 2) 100 | if pair.length == 2 101 | k, v = pair 102 | k_s = k.strip() 103 | if k_s != k 104 | msg = "In line #{line_num}, ignoring leading or trailing whitespace in key #{k.inspect}" 105 | err.call(msg) 106 | end 107 | 108 | if k_s.length == 0 109 | err.call("In line #{line_num}, got empty key") 110 | end 111 | 112 | v_s = v.strip() 113 | if v_s != v 114 | msg = "In line #{line_num}, ignoring leading or trailing whitespace in value #{v.inspect}" 115 | err.call(msg) 116 | end 117 | 118 | pairs << [k_s, v_s] 119 | else 120 | err.call("Line #{line_num} does not contain a colon") 121 | end 122 | } 123 | 124 | return pairs 125 | end 126 | 127 | def Util.dict_to_kv(d) 128 | return seq_to_kv(d.entries.sort) 129 | end 130 | 131 | def Util.kv_to_dict(s) 132 | seq = kv_to_seq(s) 133 | return Hash[*seq.flatten] 134 | end 135 | end 136 | end 137 | -------------------------------------------------------------------------------- /lib/openid/kvpost.rb: -------------------------------------------------------------------------------- 1 | require "openid/message" 2 | require "openid/fetchers" 3 | 4 | module OpenID 5 | # Exception that is raised when the server returns a 400 response 6 | # code to a direct request. 7 | class ServerError < OpenIDError 8 | attr_reader :error_text, :error_code, :message 9 | 10 | def initialize(error_text, error_code, message) 11 | super(error_text) 12 | @error_text = error_text 13 | @error_code = error_code 14 | @message = message 15 | end 16 | 17 | def self.from_message(msg) 18 | error_text = msg.get_arg(OPENID_NS, 'error', 19 | '') 20 | error_code = msg.get_arg(OPENID_NS, 'error_code') 21 | return self.new(error_text, error_code, msg) 22 | end 23 | end 24 | 25 | class KVPostNetworkError < OpenIDError 26 | end 27 | class HTTPStatusError < OpenIDError 28 | end 29 | 30 | class Message 31 | def self.from_http_response(response, server_url) 32 | msg = self.from_kvform(response.body) 33 | case response.code.to_i 34 | when 200 35 | return msg 36 | when 206 37 | return msg 38 | when 400 39 | raise ServerError.from_message(msg) 40 | else 41 | error_message = "bad status code from server #{server_url}: "\ 42 | "#{response.code}" 43 | raise HTTPStatusError.new(error_message) 44 | end 45 | end 46 | end 47 | 48 | # Send the message to the server via HTTP POST and receive and parse 49 | # a response in KV Form 50 | def self.make_kv_post(request_message, server_url) 51 | begin 52 | http_response = self.fetch(server_url, request_message.to_url_encoded) 53 | rescue Exception 54 | raise KVPostNetworkError.new("Unable to contact OpenID server: #{$!.to_s}") 55 | end 56 | return Message.from_http_response(http_response, server_url) 57 | end 58 | end 59 | -------------------------------------------------------------------------------- /lib/openid/protocolerror.rb: -------------------------------------------------------------------------------- 1 | require 'openid/util' 2 | 3 | module OpenID 4 | 5 | # An error in the OpenID protocol 6 | class ProtocolError < OpenIDError 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /lib/openid/store/interface.rb: -------------------------------------------------------------------------------- 1 | require 'openid/util' 2 | 3 | module OpenID 4 | 5 | # Stores for Associations and nonces. Used by both the Consumer and 6 | # the Server. If you have a database abstraction layer or other 7 | # state storage in your application or framework already, you can 8 | # implement the store interface. 9 | module Store 10 | # Abstract Store 11 | # Changes in 2.0: 12 | # * removed store_nonce, get_auth_key, is_dumb 13 | # * changed use_nonce to support one-way nonces 14 | # * added cleanup_nonces, cleanup_associations, cleanup 15 | class Interface < Object 16 | 17 | # Put a Association object into storage. 18 | # When implementing a store, don't assume that there are any limitations 19 | # on the character set of the server_url. In particular, expect to see 20 | # unescaped non-url-safe characters in the server_url field. 21 | def store_association(server_url, association) 22 | raise NotImplementedError 23 | end 24 | 25 | # Returns a Association object from storage that matches 26 | # the server_url. Returns nil if no such association is found or if 27 | # the one matching association is expired. (Is allowed to GC expired 28 | # associations when found.) 29 | def get_association(server_url, handle=nil) 30 | raise NotImplementedError 31 | end 32 | 33 | # If there is a matching association, remove it from the store and 34 | # return true, otherwise return false. 35 | def remove_association(server_url, handle) 36 | raise NotImplementedError 37 | end 38 | 39 | # Return true if the nonce has not been used before, and store it 40 | # for a while to make sure someone doesn't try to use the same value 41 | # again. Return false if the nonce has already been used or if the 42 | # timestamp is not current. 43 | # You can use OpenID::Store::Nonce::SKEW for your timestamp window. 44 | # server_url: URL of the server from which the nonce originated 45 | # timestamp: time the nonce was created in seconds since unix epoch 46 | # salt: A random string that makes two nonces issued by a server in 47 | # the same second unique 48 | def use_nonce(server_url, timestamp, salt) 49 | raise NotImplementedError 50 | end 51 | 52 | # Remove expired nonces from the store 53 | # Discards any nonce that is old enough that it wouldn't pass use_nonce 54 | # Not called during normal library operation, this method is for store 55 | # admins to keep their storage from filling up with expired data 56 | def cleanup_nonces 57 | raise NotImplementedError 58 | end 59 | 60 | # Remove expired associations from the store 61 | # Not called during normal library operation, this method is for store 62 | # admins to keep their storage from filling up with expired data 63 | def cleanup_associations 64 | raise NotImplementedError 65 | end 66 | 67 | # Remove expired nonces and associations from the store 68 | # Not called during normal library operation, this method is for store 69 | # admins to keep their storage from filling up with expired data 70 | def cleanup 71 | return cleanup_nonces, cleanup_associations 72 | end 73 | end 74 | end 75 | end 76 | -------------------------------------------------------------------------------- /lib/openid/store/memcache.rb: -------------------------------------------------------------------------------- 1 | require 'openid/util' 2 | require 'openid/store/interface' 3 | require 'openid/store/nonce' 4 | require 'time' 5 | 6 | module OpenID 7 | module Store 8 | class Memcache < Interface 9 | attr_accessor :key_prefix 10 | 11 | def initialize(cache_client, key_prefix='openid-store:') 12 | @cache_client = cache_client 13 | self.key_prefix = key_prefix 14 | end 15 | 16 | # Put a Association object into storage. 17 | # When implementing a store, don't assume that there are any limitations 18 | # on the character set of the server_url. In particular, expect to see 19 | # unescaped non-url-safe characters in the server_url field. 20 | def store_association(server_url, association) 21 | serialized = serialize(association) 22 | [nil, association.handle].each do |handle| 23 | key = assoc_key(server_url, handle) 24 | @cache_client.set(key, serialized, expiry(association.lifetime)) 25 | end 26 | end 27 | 28 | # Returns a Association object from storage that matches 29 | # the server_url. Returns nil if no such association is found or if 30 | # the one matching association is expired. (Is allowed to GC expired 31 | # associations when found.) 32 | def get_association(server_url, handle=nil) 33 | serialized = @cache_client.get(assoc_key(server_url, handle)) 34 | if serialized 35 | return deserialize(serialized) 36 | else 37 | return nil 38 | end 39 | end 40 | 41 | # If there is a matching association, remove it from the store and 42 | # return true, otherwise return false. 43 | def remove_association(server_url, handle) 44 | deleted = delete(assoc_key(server_url, handle)) 45 | server_assoc = get_association(server_url) 46 | if server_assoc && server_assoc.handle == handle 47 | deleted = delete(assoc_key(server_url)) | deleted 48 | end 49 | return deleted 50 | end 51 | 52 | # Return true if the nonce has not been used before, and store it 53 | # for a while to make sure someone doesn't try to use the same value 54 | # again. Return false if the nonce has already been used or if the 55 | # timestamp is not current. 56 | # You can use OpenID::Store::Nonce::SKEW for your timestamp window. 57 | # server_url: URL of the server from which the nonce originated 58 | # timestamp: time the nonce was created in seconds since unix epoch 59 | # salt: A random string that makes two nonces issued by a server in 60 | # the same second unique 61 | def use_nonce(server_url, timestamp, salt) 62 | return false if (timestamp - Time.now.to_i).abs > Nonce.skew 63 | ts = timestamp.to_s # base 10 seconds since epoch 64 | nonce_key = key_prefix + 'N' + server_url + '|' + ts + '|' + salt 65 | result = @cache_client.add(nonce_key, '', expiry(Nonce.skew + 5)) 66 | if result.is_a? String 67 | return !!(result =~ /^STORED/) 68 | else 69 | return !!(result) 70 | end 71 | end 72 | 73 | def assoc_key(server_url, assoc_handle=nil) 74 | key = key_prefix + 'A' + server_url 75 | if assoc_handle 76 | key += '|' + assoc_handle 77 | end 78 | return key 79 | end 80 | 81 | def cleanup_nonces 82 | end 83 | 84 | def cleanup 85 | end 86 | 87 | def cleanup_associations 88 | end 89 | 90 | protected 91 | 92 | def delete(key) 93 | result = @cache_client.delete(key) 94 | if result.is_a? String 95 | return !!(result =~ /^DELETED/) 96 | else 97 | return !!(result) 98 | end 99 | end 100 | 101 | def serialize(assoc) 102 | Marshal.dump(assoc) 103 | end 104 | 105 | def deserialize(assoc_str) 106 | Marshal.load(assoc_str) 107 | end 108 | 109 | # Convert a lifetime in seconds into a memcache expiry value 110 | def expiry(t) 111 | Time.now.to_i + t 112 | end 113 | end 114 | end 115 | end 116 | -------------------------------------------------------------------------------- /lib/openid/store/memory.rb: -------------------------------------------------------------------------------- 1 | require 'openid/store/interface' 2 | module OpenID 3 | module Store 4 | # An in-memory implementation of Store. This class is mainly used 5 | # for testing, though it may be useful for long-running single 6 | # process apps. Note that this store is NOT thread-safe. 7 | # 8 | # You should probably be looking at OpenID::Store::Filesystem 9 | class Memory < Interface 10 | 11 | def initialize 12 | @associations = Hash.new { |hash, key| hash[key] = {} } 13 | @nonces = {} 14 | end 15 | 16 | def store_association(server_url, assoc) 17 | assocs = @associations[server_url] 18 | @associations[server_url] = assocs.merge({assoc.handle => deepcopy(assoc)}) 19 | end 20 | 21 | def get_association(server_url, handle=nil) 22 | assocs = @associations[server_url] 23 | assoc = nil 24 | if handle 25 | assoc = assocs[handle] 26 | else 27 | assoc = assocs.values.sort{|a,b| a.issued <=> b.issued}[-1] 28 | end 29 | 30 | return assoc 31 | end 32 | 33 | def remove_association(server_url, handle) 34 | assocs = @associations[server_url] 35 | if assocs.delete(handle) 36 | return true 37 | else 38 | return false 39 | end 40 | end 41 | 42 | def use_nonce(server_url, timestamp, salt) 43 | return false if (timestamp - Time.now.to_i).abs > Nonce.skew 44 | nonce = [server_url, timestamp, salt].join('') 45 | return false if @nonces[nonce] 46 | @nonces[nonce] = timestamp 47 | return true 48 | end 49 | 50 | def cleanup_associations 51 | count = 0 52 | @associations.each{|server_url, assocs| 53 | assocs.each{|handle, assoc| 54 | if assoc.expires_in == 0 55 | assocs.delete(handle) 56 | count += 1 57 | end 58 | } 59 | } 60 | return count 61 | end 62 | 63 | def cleanup_nonces 64 | count = 0 65 | now = Time.now.to_i 66 | @nonces.each{|nonce, timestamp| 67 | if (timestamp - now).abs > Nonce.skew 68 | @nonces.delete(nonce) 69 | count += 1 70 | end 71 | } 72 | return count 73 | end 74 | 75 | protected 76 | 77 | def deepcopy(o) 78 | Marshal.load(Marshal.dump(o)) 79 | end 80 | 81 | end 82 | end 83 | end 84 | -------------------------------------------------------------------------------- /lib/openid/store/nonce.rb: -------------------------------------------------------------------------------- 1 | require 'openid/cryptutil' 2 | require 'date' 3 | require 'time' 4 | 5 | module OpenID 6 | module Nonce 7 | DEFAULT_SKEW = 60*60*5 8 | TIME_FMT = '%Y-%m-%dT%H:%M:%SZ' 9 | TIME_STR_LEN = '0000-00-00T00:00:00Z'.size 10 | @@NONCE_CHRS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" 11 | TIME_VALIDATOR = /\d\d\d\d-\d\d-\d\dT\d\d:\d\d:\d\dZ/ 12 | 13 | @skew = DEFAULT_SKEW 14 | 15 | # The allowed nonce time skew in seconds. Defaults to 5 hours. 16 | # Used for checking nonce validity, and by stores' cleanup methods. 17 | def Nonce.skew 18 | @skew 19 | end 20 | 21 | def Nonce.skew=(new_skew) 22 | @skew = new_skew 23 | end 24 | 25 | # Extract timestamp from a nonce string 26 | def Nonce.split_nonce(nonce_str) 27 | timestamp_str = nonce_str[0...TIME_STR_LEN] 28 | raise ArgumentError if timestamp_str.size < TIME_STR_LEN 29 | raise ArgumentError unless timestamp_str.match(TIME_VALIDATOR) 30 | ts = Time.parse(timestamp_str).to_i 31 | raise ArgumentError if ts < 0 32 | return ts, nonce_str[TIME_STR_LEN..-1] 33 | end 34 | 35 | # Is the timestamp that is part of the specified nonce string 36 | # within the allowed clock-skew of the current time? 37 | def Nonce.check_timestamp(nonce_str, allowed_skew=nil, now=nil) 38 | allowed_skew = skew if allowed_skew.nil? 39 | begin 40 | stamp, _ = split_nonce(nonce_str) 41 | rescue ArgumentError # bad timestamp 42 | return false 43 | end 44 | now = Time.now.to_i unless now 45 | 46 | # times before this are too old 47 | past = now - allowed_skew 48 | 49 | # times newer than this are too far in the future 50 | future = now + allowed_skew 51 | 52 | return (past <= stamp and stamp <= future) 53 | end 54 | 55 | # generate a nonce with the specified timestamp (defaults to now) 56 | def Nonce.mk_nonce(time = nil) 57 | salt = CryptUtil::random_string(6, @@NONCE_CHRS) 58 | if time.nil? 59 | t = Time.now.getutc 60 | else 61 | t = Time.at(time).getutc 62 | end 63 | time_str = t.strftime(TIME_FMT) 64 | return time_str + salt 65 | end 66 | 67 | end 68 | end 69 | -------------------------------------------------------------------------------- /lib/openid/urinorm.rb: -------------------------------------------------------------------------------- 1 | require 'uri' 2 | 3 | module OpenID 4 | 5 | module URINorm 6 | public 7 | def URINorm.urinorm(uri) 8 | uri = URI.parse(uri) 9 | 10 | raise URI::InvalidURIError.new('no scheme') unless uri.scheme 11 | uri.scheme = uri.scheme.downcase 12 | unless ['http','https'].member?(uri.scheme) 13 | raise URI::InvalidURIError.new('Not an HTTP or HTTPS URI') 14 | end 15 | 16 | raise URI::InvalidURIError.new('no host') unless uri.host 17 | uri.host = uri.host.downcase 18 | 19 | uri.path = remove_dot_segments(uri.path) 20 | uri.path = '/' if uri.path.length == 0 21 | 22 | uri = uri.normalize.to_s 23 | uri = uri.gsub(PERCENT_ESCAPE_RE) { 24 | sub = $&[1..2].to_i(16).chr 25 | reserved(sub) ? $&.upcase : sub 26 | } 27 | 28 | return uri 29 | end 30 | 31 | private 32 | RESERVED_RE = /[A-Za-z0-9._~-]/ 33 | PERCENT_ESCAPE_RE = /%[0-9a-zA-Z]{2}/ 34 | 35 | def URINorm.reserved(chr) 36 | not RESERVED_RE =~ chr 37 | end 38 | 39 | def URINorm.remove_dot_segments(path) 40 | result_segments = [] 41 | 42 | while path.length > 0 43 | if path.start_with?('../') 44 | path = path[3..-1] 45 | elsif path.start_with?('./') 46 | path = path[2..-1] 47 | elsif path.start_with?('/./') 48 | path = path[2..-1] 49 | elsif path == '/.' 50 | path = '/' 51 | elsif path.start_with?('/../') 52 | path = path[3..-1] 53 | result_segments.pop if result_segments.length > 0 54 | elsif path == '/..' 55 | path = '/' 56 | result_segments.pop if result_segments.length > 0 57 | elsif path == '..' or path == '.' 58 | path = '' 59 | else 60 | i = 0 61 | i = 1 if path[0].chr == '/' 62 | i = path.index('/', i) 63 | i = path.length if i.nil? 64 | result_segments << path[0...i] 65 | path = path[i..-1] 66 | end 67 | end 68 | 69 | return result_segments.join('') 70 | end 71 | end 72 | 73 | end 74 | -------------------------------------------------------------------------------- /lib/openid/util.rb: -------------------------------------------------------------------------------- 1 | require "cgi" 2 | require "uri" 3 | require "logger" 4 | 5 | # See OpenID::Consumer or OpenID::Server modules, as well as the store classes 6 | module OpenID 7 | class AssertionError < Exception 8 | end 9 | 10 | # Exceptions that are raised by the library are subclasses of this 11 | # exception type, so if you want to catch all exceptions raised by 12 | # the library, you can catch OpenIDError 13 | class OpenIDError < StandardError 14 | end 15 | 16 | module Util 17 | 18 | BASE64_CHARS = ('ABCDEFGHIJKLMNOPQRSTUVWXYZ' \ 19 | 'abcdefghijklmnopqrstuvwxyz0123456789+/') 20 | BASE64_RE = Regexp.compile(" 21 | \\A 22 | ([#{BASE64_CHARS}]{4})* 23 | ([#{BASE64_CHARS}]{2}==| 24 | [#{BASE64_CHARS}]{3}=)? 25 | \\Z", Regexp::EXTENDED) 26 | 27 | def Util.assert(value, message=nil) 28 | if not value 29 | raise AssertionError, message or value 30 | end 31 | end 32 | 33 | def Util.to_base64(s) 34 | [s].pack('m').gsub("\n", "") 35 | end 36 | 37 | def Util.from_base64(s) 38 | without_newlines = s.gsub(/[\r\n]+/, '') 39 | if !BASE64_RE.match(without_newlines) 40 | raise ArgumentError, "Malformed input: #{s.inspect}" 41 | end 42 | without_newlines.unpack('m').first 43 | end 44 | 45 | def Util.urlencode(args) 46 | a = [] 47 | args.each do |key, val| 48 | if val.nil? 49 | val = '' 50 | elsif !!val == val 51 | #it's boolean let's convert it to string representation 52 | # or else CGI::escape won't like it 53 | val = val.to_s 54 | end 55 | 56 | a << (CGI::escape(key) + "=" + CGI::escape(val)) 57 | end 58 | a.join("&") 59 | end 60 | 61 | def Util.parse_query(qs) 62 | query = {} 63 | CGI::parse(qs).each {|k,v| query[k] = v[0]} 64 | return query 65 | end 66 | 67 | def Util.append_args(url, args) 68 | url = url.dup 69 | return url if args.length == 0 70 | 71 | if args.respond_to?('each_pair') 72 | args = args.sort 73 | end 74 | 75 | url << (url.include?("?") ? "&" : "?") 76 | url << Util.urlencode(args) 77 | end 78 | 79 | @@logger = Logger.new(STDERR) 80 | @@logger.progname = "OpenID" 81 | 82 | def Util.logger=(logger) 83 | @@logger = logger 84 | end 85 | 86 | def Util.logger 87 | @@logger 88 | end 89 | 90 | # change the message below to do whatever you like for logging 91 | def Util.log(message) 92 | logger.info(message) 93 | end 94 | 95 | def Util.auto_submit_html(form, title='OpenID transaction in progress') 96 | return " 97 | 98 | 99 | #{title} 100 | 101 | 102 | #{form} 103 | 109 | 110 | 111 | " 112 | end 113 | 114 | ESCAPE_TABLE = { '&' => '&', '<' => '<', '>' => '>', '"' => '"', "'" => ''' } 115 | # Modified from ERb's html_encode 116 | def Util.html_encode(str) 117 | str.to_s.gsub(/[&<>"']/) {|s| ESCAPE_TABLE[s] } 118 | end 119 | end 120 | 121 | end 122 | -------------------------------------------------------------------------------- /lib/openid/version.rb: -------------------------------------------------------------------------------- 1 | module OpenID 2 | VERSION = "2.9.2" 3 | end 4 | -------------------------------------------------------------------------------- /lib/openid/yadis/accept.rb: -------------------------------------------------------------------------------- 1 | module OpenID 2 | 3 | module Yadis 4 | 5 | # Generate an accept header value 6 | # 7 | # [str or (str, float)] -> str 8 | def self.generate_accept_header(*elements) 9 | parts = [] 10 | elements.each { |element| 11 | if element.is_a?(String) 12 | qs = "1.0" 13 | mtype = element 14 | else 15 | mtype, q = element 16 | q = q.to_f 17 | if q > 1 or q <= 0 18 | raise ArgumentError.new("Invalid preference factor: #{q}") 19 | end 20 | qs = sprintf("%0.1f", q) 21 | end 22 | 23 | parts << [qs, mtype] 24 | } 25 | 26 | parts.sort! 27 | chunks = [] 28 | parts.each { |q, mtype| 29 | if q == '1.0' 30 | chunks << mtype 31 | else 32 | chunks << sprintf("%s; q=%s", mtype, q) 33 | end 34 | } 35 | 36 | return chunks.join(', ') 37 | end 38 | 39 | def self.parse_accept_header(value) 40 | # Parse an accept header, ignoring any accept-extensions 41 | # 42 | # returns a list of tuples containing main MIME type, MIME 43 | # subtype, and quality markdown. 44 | # 45 | # str -> [(str, str, float)] 46 | chunks = value.split(',', -1).collect { |v| v.strip } 47 | accept = [] 48 | chunks.each { |chunk| 49 | parts = chunk.split(";", -1).collect { |s| s.strip } 50 | 51 | mtype = parts.shift 52 | if mtype.index('/').nil? 53 | # This is not a MIME type, so ignore the bad data 54 | next 55 | end 56 | 57 | main, sub = mtype.split('/', 2) 58 | 59 | q = nil 60 | parts.each { |ext| 61 | if !ext.index('=').nil? 62 | k, v = ext.split('=', 2) 63 | if k == 'q' 64 | q = v.to_f 65 | end 66 | end 67 | } 68 | 69 | q = 1.0 if q.nil? 70 | 71 | accept << [q, main, sub] 72 | } 73 | 74 | accept.sort! 75 | accept.reverse! 76 | 77 | return accept.collect { |q, main, sub| [main, sub, q] } 78 | end 79 | 80 | def self.match_types(accept_types, have_types) 81 | # Given the result of parsing an Accept: header, and the 82 | # available MIME types, return the acceptable types with their 83 | # quality markdowns. 84 | # 85 | # For example: 86 | # 87 | # >>> acceptable = parse_accept_header('text/html, text/plain; q=0.5') 88 | # >>> matchTypes(acceptable, ['text/plain', 'text/html', 'image/jpeg']) 89 | # [('text/html', 1.0), ('text/plain', 0.5)] 90 | # 91 | # Type signature: ([(str, str, float)], [str]) -> [(str, float)] 92 | if accept_types.nil? or accept_types == [] 93 | # Accept all of them 94 | default = 1 95 | else 96 | default = 0 97 | end 98 | 99 | match_main = {} 100 | match_sub = {} 101 | accept_types.each { |main, sub, q| 102 | if main == '*' 103 | default = [default, q].max 104 | next 105 | elsif sub == '*' 106 | match_main[main] = [match_main.fetch(main, 0), q].max 107 | else 108 | match_sub[[main, sub]] = [match_sub.fetch([main, sub], 0), q].max 109 | end 110 | } 111 | 112 | accepted_list = [] 113 | order_maintainer = 0 114 | have_types.each { |mtype| 115 | main, sub = mtype.split('/', 2) 116 | if match_sub.member?([main, sub]) 117 | q = match_sub[[main, sub]] 118 | else 119 | q = match_main.fetch(main, default) 120 | end 121 | 122 | if q != 0 123 | accepted_list << [1 - q, order_maintainer, q, mtype] 124 | order_maintainer += 1 125 | end 126 | } 127 | 128 | accepted_list.sort! 129 | return accepted_list.collect { |_, _, q, mtype| [mtype, q] } 130 | end 131 | 132 | def self.get_acceptable(accept_header, have_types) 133 | # Parse the accept header and return a list of available types 134 | # in preferred order. If a type is unacceptable, it will not be 135 | # in the resulting list. 136 | # 137 | # This is a convenience wrapper around matchTypes and 138 | # parse_accept_header 139 | # 140 | # (str, [str]) -> [str] 141 | accepted = self.parse_accept_header(accept_header) 142 | preferred = self.match_types(accepted, have_types) 143 | return preferred.collect { |mtype, _| mtype } 144 | end 145 | 146 | end 147 | 148 | end 149 | -------------------------------------------------------------------------------- /lib/openid/yadis/constants.rb: -------------------------------------------------------------------------------- 1 | 2 | require 'openid/yadis/accept' 3 | 4 | module OpenID 5 | 6 | module Yadis 7 | 8 | YADIS_HEADER_NAME = 'X-XRDS-Location' 9 | YADIS_CONTENT_TYPE = 'application/xrds+xml' 10 | 11 | # A value suitable for using as an accept header when performing 12 | # YADIS discovery, unless the application has special requirements 13 | YADIS_ACCEPT_HEADER = generate_accept_header( 14 | ['text/html', 0.3], 15 | ['application/xhtml+xml', 0.5], 16 | [YADIS_CONTENT_TYPE, 1.0] 17 | ) 18 | 19 | end 20 | 21 | end 22 | -------------------------------------------------------------------------------- /lib/openid/yadis/parsehtml.rb: -------------------------------------------------------------------------------- 1 | require "openid/yadis/htmltokenizer" 2 | require 'cgi' 3 | 4 | module OpenID 5 | module Yadis 6 | def Yadis.html_yadis_location(html) 7 | parser = HTMLTokenizer.new(html) 8 | 9 | # to keep track of whether or not we are in the head element 10 | in_head = false 11 | 12 | begin 13 | while el = parser.getTag('head', '/head', 'meta', 'body', '/body', 14 | 'html', 'script') 15 | 16 | # we are leaving head or have reached body, so we bail 17 | return nil if ['/head', 'body', '/body'].member?(el.tag_name) 18 | 19 | if el.tag_name == 'head' 20 | unless el.to_s[-2] == ?/ # tag ends with a /: a short tag 21 | in_head = true 22 | end 23 | end 24 | next unless in_head 25 | 26 | if el.tag_name == 'script' 27 | unless el.to_s[-2] == ?/ # tag ends with a /: a short tag 28 | parser.getTag('/script') 29 | end 30 | end 31 | 32 | return nil if el.tag_name == 'html' 33 | 34 | if el.tag_name == 'meta' and (equiv = el.attr_hash['http-equiv']) 35 | if ['x-xrds-location','x-yadis-location'].member?(equiv.downcase) && 36 | el.attr_hash.member?('content') 37 | return CGI::unescapeHTML(el.attr_hash['content']) 38 | end 39 | end 40 | end 41 | rescue HTMLTokenizerError # just stop parsing if there's an error 42 | end 43 | end 44 | end 45 | end 46 | -------------------------------------------------------------------------------- /lib/openid/yadis/services.rb: -------------------------------------------------------------------------------- 1 | 2 | require 'openid/yadis/filters' 3 | require 'openid/yadis/discovery' 4 | require 'openid/yadis/xrds' 5 | 6 | module OpenID 7 | module Yadis 8 | def Yadis.get_service_endpoints(input_url, flt=nil) 9 | # Perform the Yadis protocol on the input URL and return an 10 | # iterable of resulting endpoint objects. 11 | # 12 | # @param flt: A filter object or something that is convertable 13 | # to a filter object (using mkFilter) that will be used to 14 | # generate endpoint objects. This defaults to generating 15 | # BasicEndpoint objects. 16 | result = Yadis.discover(input_url) 17 | begin 18 | endpoints = Yadis.apply_filter(result.normalized_uri, 19 | result.response_text, flt) 20 | rescue XRDSError => err 21 | raise DiscoveryFailure.new(err.to_s, nil) 22 | end 23 | 24 | return [result.normalized_uri, endpoints] 25 | end 26 | 27 | def Yadis.apply_filter(normalized_uri, xrd_data, flt=nil) 28 | # Generate an iterable of endpoint objects given this input data, 29 | # presumably from the result of performing the Yadis protocol. 30 | 31 | flt = Yadis.make_filter(flt) 32 | et = Yadis.parseXRDS(xrd_data) 33 | 34 | endpoints = [] 35 | each_service(et) { |service_element| 36 | endpoints += flt.get_service_endpoints(normalized_uri, service_element) 37 | } 38 | 39 | return endpoints 40 | end 41 | end 42 | end 43 | -------------------------------------------------------------------------------- /lib/openid/yadis/xri.rb: -------------------------------------------------------------------------------- 1 | require 'openid/fetchers' 2 | 3 | module OpenID 4 | module Yadis 5 | module XRI 6 | 7 | # The '(' is for cross-reference authorities, and hopefully has a 8 | # matching ')' somewhere. 9 | XRI_AUTHORITIES = ["!", "=", "@", "+", "$", "("] 10 | 11 | def self.identifier_scheme(identifier) 12 | if (!identifier.nil? and 13 | identifier.length > 0 and 14 | (identifier.match('^xri://') or 15 | XRI_AUTHORITIES.member?(identifier[0].chr))) 16 | return :xri 17 | else 18 | return :uri 19 | end 20 | end 21 | 22 | # Transform an XRI reference to an IRI reference. Note this is 23 | # not not idempotent, so do not apply this to an identifier more 24 | # than once. XRI Syntax section 2.3.1 25 | def self.to_iri_normal(xri) 26 | iri = xri.dup 27 | iri.insert(0, 'xri://') if not iri.match('^xri://') 28 | return escape_for_iri(iri) 29 | end 30 | 31 | # Note this is not not idempotent, so do not apply this more than 32 | # once. XRI Syntax section 2.3.2 33 | def self.escape_for_iri(xri) 34 | esc = xri.dup 35 | # encode all % 36 | esc.gsub!(/%/, '%25') 37 | esc.gsub!(/\((.*?)\)/) { |xref_match| 38 | xref_match.gsub(/[\/\?\#]/) { |char_match| 39 | CGI::escape(char_match) 40 | } 41 | } 42 | return esc 43 | end 44 | 45 | # Transform an XRI reference to a URI reference. Note this is not 46 | # not idempotent, so do not apply this to an identifier more than 47 | # once. XRI Syntax section 2.3.1 48 | def self.to_uri_normal(xri) 49 | return iri_to_uri(to_iri_normal(xri)) 50 | end 51 | 52 | # RFC 3987 section 3.1 53 | def self.iri_to_uri(iri) 54 | uri = iri.dup 55 | # for char in ucschar or iprivate 56 | # convert each char to %HH%HH%HH (as many %HH as octets) 57 | return uri 58 | end 59 | 60 | def self.provider_is_authoritative(provider_id, canonical_id) 61 | lastbang = canonical_id.rindex('!') 62 | return false unless lastbang 63 | parent = canonical_id[0...lastbang] 64 | return parent == provider_id 65 | end 66 | 67 | def self.root_authority(xri) 68 | xri = xri[6..-1] if xri.index('xri://') == 0 69 | authority = xri.split('/', 2)[0] 70 | if authority[0].chr == '(' 71 | root = authority[0...authority.index(')')+1] 72 | elsif XRI_AUTHORITIES.member?(authority[0].chr) 73 | root = authority[0].chr 74 | else 75 | root = authority.split(/[!*]/)[0] 76 | end 77 | 78 | self.make_xri(root) 79 | end 80 | 81 | def self.make_xri(xri) 82 | if xri.index('xri://') != 0 83 | xri = 'xri://' + xri 84 | end 85 | return xri 86 | end 87 | end 88 | end 89 | end 90 | -------------------------------------------------------------------------------- /lib/openid/yadis/xrires.rb: -------------------------------------------------------------------------------- 1 | require "cgi" 2 | require "openid/yadis/xri" 3 | require "openid/yadis/xrds" 4 | require "openid/fetchers" 5 | 6 | module OpenID 7 | 8 | module Yadis 9 | 10 | module XRI 11 | 12 | class XRIHTTPError < StandardError; end 13 | 14 | class ProxyResolver 15 | 16 | DEFAULT_PROXY = 'http://proxy.xri.net/' 17 | 18 | def initialize(proxy_url=nil) 19 | if proxy_url 20 | @proxy_url = proxy_url 21 | else 22 | @proxy_url = DEFAULT_PROXY 23 | end 24 | 25 | @proxy_url += '/' unless @proxy_url.match('/$') 26 | end 27 | 28 | def query_url(xri, service_type=nil) 29 | # URI normal form has a leading xri://, but we need to strip 30 | # that off again for the QXRI. This is under discussion for 31 | # XRI Resolution WD 11. 32 | qxri = XRI.to_uri_normal(xri)[6..-1] 33 | hxri = @proxy_url + qxri 34 | args = {'_xrd_r' => 'application/xrds+xml'} 35 | if service_type 36 | args['_xrd_t'] = service_type 37 | else 38 | # don't perform service endpoint selection 39 | args['_xrd_r'] += ';sep=false' 40 | end 41 | 42 | return XRI.append_args(hxri, args) 43 | end 44 | 45 | def query(xri) 46 | # these can be query args or http headers, needn't be both. 47 | # headers = {'Accept' => 'application/xrds+xml;sep=true'} 48 | canonicalID = nil 49 | 50 | url = self.query_url(xri) 51 | begin 52 | response = OpenID.fetch(url) 53 | rescue 54 | raise XRIHTTPError, "Could not fetch #{xri}, #{$!}" 55 | end 56 | raise XRIHTTPError, "Could not fetch #{xri}" if response.nil? 57 | 58 | xrds = Yadis::parseXRDS(response.body) 59 | canonicalID = Yadis::get_canonical_id(xri, xrds) 60 | 61 | return canonicalID, Yadis::services(xrds) 62 | # TODO: 63 | # * If we do get hits for multiple service_types, we're almost 64 | # certainly going to have duplicated service entries and 65 | # broken priority ordering. 66 | end 67 | end 68 | 69 | def self.urlencode(args) 70 | a = [] 71 | args.each do |key, val| 72 | a << (CGI::escape(key) + "=" + CGI::escape(val)) 73 | end 74 | a.join("&") 75 | end 76 | 77 | def self.append_args(url, args) 78 | return url if args.length == 0 79 | 80 | # rstrip question marks 81 | rstripped = url.dup 82 | while rstripped[-1].chr == '?' 83 | rstripped = rstripped[0...rstripped.length-1] 84 | end 85 | 86 | if rstripped.index('?') 87 | sep = '&' 88 | else 89 | sep = '?' 90 | end 91 | 92 | return url + sep + XRI.urlencode(args) 93 | end 94 | 95 | end 96 | 97 | end 98 | 99 | end 100 | -------------------------------------------------------------------------------- /lib/ruby-openid.rb: -------------------------------------------------------------------------------- 1 | require 'openid' 2 | -------------------------------------------------------------------------------- /ruby-openid.gemspec: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | require File.expand_path('../lib/openid/version', __FILE__) 3 | 4 | Gem::Specification.new do |s| 5 | s.name = 'ruby-openid' 6 | s.author = 'JanRain, Inc' 7 | s.email = 'openid@janrain.com' 8 | s.homepage = 'https://github.com/openid/ruby-openid' 9 | s.summary = 'A library for consuming and serving OpenID identities.' 10 | s.version = OpenID::VERSION 11 | s.licenses = ["Ruby", "Apache Software License 2.0"] 12 | 13 | # Files 14 | files = Dir.glob("{examples,lib,test}/**/*") 15 | files << 'NOTICE' << 'CHANGELOG.md' 16 | s.files = files.delete_if {|f| f.include?('_darcs') || f.include?('admin')} 17 | s.require_paths = ['lib'] 18 | s.executables = s.files.grep(%r{^bin/}).map{ |f| File.basename(f) } 19 | s.test_files = s.files.grep(%r{^(test|spec|features)/}) 20 | 21 | # RDoc 22 | s.extra_rdoc_files = ['README.md', 'INSTALL.md', 'LICENSE', 'UPGRADE.md'] 23 | s.rdoc_options << '--main' << 'README.md' 24 | 25 | s.add_development_dependency 'minitest', '>= 5' 26 | end 27 | -------------------------------------------------------------------------------- /test/data/accept.txt: -------------------------------------------------------------------------------- 1 | # Accept: [Accept: header value from RFC2616, 2 | # http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html] 3 | # Available: [whitespace-separated content types] 4 | # Expected: [Accept-header like list, containing the available content 5 | # types with their q-values] 6 | 7 | Accept: */* 8 | Available: text/plain 9 | Expected: text/plain; q=1.0 10 | 11 | Accept: */* 12 | Available: text/plain, text/html 13 | Expected: text/plain; q=1.0, text/html; q=1.0 14 | 15 | # The order matters 16 | Accept: */* 17 | Available: text/html, text/plain 18 | Expected: text/html; q=1.0, text/plain; q=1.0 19 | 20 | Accept: text/*, */*; q=0.9 21 | Available: text/plain, image/jpeg 22 | Expected: text/plain; q=1.0, image/jpeg; q=0.9 23 | 24 | Accept: text/*, */*; q=0.9 25 | Available: image/jpeg, text/plain 26 | Expected: text/plain; q=1.0, image/jpeg; q=0.9 27 | 28 | # wildcard subtypes still reject differing main types 29 | Accept: text/* 30 | Available: image/jpeg, text/plain 31 | Expected: text/plain; q=1.0 32 | 33 | Accept: text/html 34 | Available: text/html 35 | Expected: text/html; q=1.0 36 | 37 | Accept: text/html, text/* 38 | Available: text/html 39 | Expected: text/html; q=1.0 40 | 41 | Accept: text/html, text/* 42 | Available: text/plain, text/html 43 | Expected: text/plain; q=1.0, text/html; q=1.0 44 | 45 | Accept: text/html, text/*; q=0.9 46 | Available: text/plain, text/html 47 | Expected: text/html; q=1.0, text/plain; q=0.9 48 | 49 | # If a more specific type has a higher q-value, then the higher value wins 50 | Accept: text/*; q=0.9, text/html 51 | Available: text/plain, text/html 52 | Expected: text/html; q=1.0, text/plain; q=0.9 53 | 54 | Accept: */*, text/*; q=0.9, text/html; q=0.1 55 | Available: text/plain, text/html, image/monkeys 56 | Expected: image/monkeys; q=1.0, text/plain; q=0.9, text/html; q=0.1 57 | 58 | Accept: text/*, text/html; q=0 59 | Available: text/html 60 | Expected: 61 | 62 | Accept: text/*, text/html; q=0 63 | Available: text/html, text/plain 64 | Expected: text/plain; q=1.0 65 | 66 | Accept: text/html 67 | Available: text/plain 68 | Expected: 69 | 70 | Accept: application/xrds+xml, text/html; q=0.9 71 | Available: application/xrds+xml, text/html 72 | Expected: application/xrds+xml; q=1.0, text/html; q=0.9 73 | 74 | Accept: application/xrds+xml, */*; q=0.9 75 | Available: application/xrds+xml, text/html 76 | Expected: application/xrds+xml; q=1.0, text/html; q=0.9 77 | 78 | Accept: application/xrds+xml, application/xhtml+xml; q=0.9, text/html; q=0.8, text/xml; q=0.7 79 | Available: application/xrds+xml, text/html 80 | Expected: application/xrds+xml; q=1.0, text/html; q=0.8 81 | 82 | # See http://www.rfc-editor.org/rfc/rfc3023.txt, section A.13 83 | Accept: application/xrds 84 | Available: application/xrds+xml 85 | Expected: 86 | 87 | Accept: application/xrds+xml 88 | Available: application/xrds 89 | Expected: 90 | 91 | Accept: application/xml 92 | Available: application/xrds+xml 93 | Expected: 94 | 95 | Available: application/xrds+xml 96 | Accept: application/xml 97 | Expected: 98 | 99 | Available: 100 | Accept: not_a_content_type 101 | Expected: 102 | 103 | Available: text/html 104 | Accept: not_a_content_type, text/html 105 | Expected: text/html; q=1.0 106 | 107 | ################################################# 108 | # The tests below this line are documentation of how this library 109 | # works. If the implementation changes, it's acceptable to change the 110 | # test to reflect that. These are specified so that we can make sure 111 | # that the current implementation actually works the way that we 112 | # expect it to given these inputs. 113 | 114 | Accept: text/html;level=1 115 | Available: text/html 116 | Expected: text/html; q=1.0 117 | 118 | Accept: text/html; level=1, text/html; level=9; q=0.1 119 | Available: text/html 120 | Expected: text/html; q=1.0 121 | 122 | Accept: text/html; level=9; q=0.1, text/html; level=1 123 | Available: text/html 124 | Expected: text/html; q=1.0 125 | -------------------------------------------------------------------------------- /test/data/example-xrds.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | 9 | http://example.com/ 10 | http://www.openidenabled.com/ 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /test/data/test1-discover.txt: -------------------------------------------------------------------------------- 1 | equiv 2 | Status: 200 OK 3 | Content-Type: text/html 4 | 5 | 6 | 7 | 8 | Joe Schmoe's Homepage 9 | 10 | 11 |

Joe Schmoe's Homepage

12 |

Blah blah blah blah blah blah blah

13 | 14 | 15 | 16 | header 17 | Status: 200 OK 18 | Content-Type: text/html 19 | YADIS_HEADER: URL_BASE/xrds 20 | 21 | 22 | 23 | Joe Schmoe's Homepage 24 | 25 | 26 |

Joe Schmoe's Homepage

27 |

Blah blah blah blah blah blah blah

28 | 29 | 30 | xrds 31 | Status: 200 OK 32 | Content-Type: application/xrds+xml 33 | 34 | 35 | 36 | xrds_ctparam 37 | Status: 200 OK 38 | Content-Type: application/xrds+xml; charset=UTF8 39 | 40 | 41 | 42 | xrds_ctcase 43 | Status: 200 OK 44 | Content-Type: appliCATION/XRDS+xml 45 | 46 | 47 | 48 | xrds_html 49 | Status: 200 OK 50 | Content-Type: text/html 51 | 52 | 53 | 54 | redir_equiv 55 | Status: 302 Found 56 | Content-Type: text/plain 57 | Location: URL_BASE/equiv 58 | 59 | You are presently being redirected. 60 | 61 | redir_header 62 | Status: 302 Found 63 | Content-Type: text/plain 64 | Location: URL_BASE/header 65 | 66 | You are presently being redirected. 67 | 68 | redir_xrds 69 | Status: 302 Found 70 | Content-Type: application/xrds+xml 71 | Location: URL_BASE/xrds 72 | 73 | 74 | 75 | redir_xrds_html 76 | Status: 302 Found 77 | Content-Type: text/plain 78 | Location: URL_BASE/xrds_html 79 | 80 | You are presently being redirected. 81 | 82 | redir_redir_equiv 83 | Status: 302 Found 84 | Content-Type: text/plain 85 | Location: URL_BASE/redir_equiv 86 | 87 | You are presently being redirected. 88 | 89 | lowercase_header 90 | Status: 200 OK 91 | Content-Type: text/html 92 | x-xrds-location: URL_BASE/xrds 93 | 94 | 95 | 96 | Joe Schmoe's Homepage 97 | 98 | 99 |

Joe Schmoe's Homepage

100 |

Blah blah blah blah blah blah blah

101 | 102 | 103 | 404_server_response 104 | Status: 404 Not Found 105 | 106 | EEk! 107 | 108 | 500_server_response 109 | Status: 500 Server error 110 | 111 | EEk! 112 | 113 | 201_server_response 114 | Status: 201 Created 115 | 116 | EEk! 117 | 118 | 404_with_header 119 | Status: 404 Not Found 120 | YADIS_HEADER: URL_BASE/xrds 121 | 122 | EEk! 123 | 124 | 404_with_meta 125 | Status: 404 Not Found 126 | Content-Type: text/html 127 | 128 | 129 | 130 | 131 | Joe Schmoe's Homepage 132 | 133 | 134 |

Joe Schmoe's Homepage

135 |

Blah blah blah blah blah blah blah

136 | 137 | 138 | -------------------------------------------------------------------------------- /test/data/test1-parsehtml.txt: -------------------------------------------------------------------------------- 1 | found 2 | 3 | 4 | 5 | found 6 | 7 | 8 | 9 | found 10 | 11 | 12 | 13 | found 14 | 15 | 16 | 17 | found 18 | 19 | 20 | 21 | found 22 | 23 | 24 | 25 | found 26 | 27 | 28 | 29 | found 30 | 31 | 32 | 33 | None 34 | 35 |