├── admin ├── library-name ├── fixperms ├── build-docs ├── runtests ├── prepare-release ├── mkassoc ├── graph-require.sh └── gettlds.py ├── examples ├── rails_openid │ ├── log │ │ └── .gitkeep │ ├── app │ │ ├── mailers │ │ │ └── .gitkeep │ │ ├── models │ │ │ └── .gitkeep │ │ ├── helpers │ │ │ ├── login_helper.rb │ │ │ ├── application_helper.rb │ │ │ └── server_helper.rb │ │ ├── assets │ │ │ ├── images │ │ │ │ └── rails.png │ │ │ ├── stylesheets │ │ │ │ └── application.css │ │ │ └── javascripts │ │ │ │ └── application.js │ │ ├── controllers │ │ │ ├── application_controller.rb │ │ │ └── login_controller.rb │ │ └── views │ │ │ ├── server │ │ │ └── decide.html.erb │ │ │ ├── layouts │ │ │ └── server.html.erb │ │ │ ├── login │ │ │ └── index.html.erb │ │ │ └── consumer │ │ │ └── index.html.erb │ ├── lib │ │ ├── assets │ │ │ └── .gitkeep │ │ └── tasks │ │ │ └── .gitkeep │ ├── public │ │ ├── favicon.ico │ │ ├── images │ │ │ └── openid_login_bg.gif │ │ ├── javascripts │ │ │ └── application.js │ │ ├── robots.txt │ │ ├── dispatch.cgi │ │ ├── dispatch.rb │ │ ├── 500.html │ │ ├── 422.html │ │ ├── 404.html │ │ └── dispatch.fcgi │ ├── test │ │ ├── unit │ │ │ └── .gitkeep │ │ ├── fixtures │ │ │ └── .gitkeep │ │ ├── functional │ │ │ ├── .gitkeep │ │ │ ├── login_controller_test.rb │ │ │ └── server_controller_test.rb │ │ ├── integration │ │ │ └── .gitkeep │ │ ├── performance │ │ │ └── browsing_test.rb │ │ └── test_helper.rb │ ├── db │ │ ├── development.sqlite3 │ │ └── seeds.rb │ ├── config │ │ ├── initializers │ │ │ ├── rails_root.rb │ │ │ ├── mime_types.rb │ │ │ ├── backtrace_silencers.rb │ │ │ ├── session_store.rb │ │ │ ├── secret_token.rb │ │ │ ├── wrap_parameters.rb │ │ │ └── inflections.rb │ │ ├── environment.rb │ │ ├── boot.rb │ │ ├── locales │ │ │ └── en.yml │ │ ├── database.yml │ │ ├── environments │ │ │ ├── development.rb │ │ │ ├── test.rb │ │ │ └── production.rb │ │ ├── routes.rb │ │ └── application.rb │ ├── config.ru │ ├── doc │ │ └── README_FOR_APP │ ├── Rakefile │ ├── script │ │ └── rails │ └── Gemfile ├── active_record_openid_store │ ├── lib │ │ ├── nonce.rb │ │ ├── open_id_setting.rb │ │ ├── association.rb │ │ └── openid_ar_store.rb │ ├── init.rb │ ├── XXX_upgrade_open_id_store.rb │ ├── XXX_add_open_id_store_to_db.rb │ └── README ├── discover └── README.md ├── lib ├── ruby-openid.rb ├── openid │ ├── version.rb │ ├── protocolerror.rb │ ├── yadis │ │ ├── constants.rb │ │ ├── services.rb │ │ ├── parsehtml.rb │ │ ├── xri.rb │ │ ├── xrires.rb │ │ └── accept.rb │ ├── consumer │ │ ├── session.rb │ │ ├── html_parse.rb │ │ └── discovery_manager.rb │ ├── extension.rb │ ├── extensions │ │ ├── ui.rb │ │ └── oauth.rb │ ├── kvpost.rb │ ├── urinorm.rb │ ├── store │ │ ├── nonce.rb │ │ ├── memory.rb │ │ ├── interface.rb │ │ └── memcache.rb │ ├── dh.rb │ ├── util.rb │ ├── cryptutil.rb │ └── kvform.rb ├── hmac │ ├── sha1.rb │ ├── sha2.rb │ └── hmac.rb └── openid.rb ├── test ├── data │ ├── test_xrds │ │ ├── not-xrds.xml │ │ ├── no-xrd.xml │ │ ├── status222.xrds │ │ ├── README │ │ ├── spoof1.xrds │ │ ├── spoof2.xrds │ │ ├── =j3h.2007.11.14.xrds │ │ ├── spoof3.xrds │ │ ├── delegated-20060809.xrds │ │ ├── valid-populated-xrds.xml │ │ ├── delegated-20060809-r1.xrds │ │ ├── delegated-20060809-r2.xrds │ │ ├── prefixsometimes.xrds │ │ ├── sometimesprefix.xrds │ │ └── subsegments.xrds │ ├── test_discover │ │ ├── yadis_no_delegate.xml │ │ ├── openid_no_delegate.html │ │ ├── openid2_xrds_no_local_id.xml │ │ ├── yadis_0entries.xml │ │ ├── openid2_xrds.xml │ │ ├── yadis_idp.xml │ │ ├── openid.html │ │ ├── openid2.html │ │ ├── openid_utf8.html │ │ ├── openid_1_and_2.html │ │ ├── yadis_idp_delegate.xml │ │ ├── yadis_another_delegate.xml │ │ ├── openid_and_yadis.html │ │ ├── malformed_meta_tag.html │ │ ├── yadis_2_bad_local_id.xml │ │ ├── openid_1_and_2_xrds.xml │ │ ├── openid_1_and_2_xrds_bad_delegate.xml │ │ ├── yadis_2entries_idp.xml │ │ └── yadis_2entries_delegate.xml │ ├── example-xrds.xml │ ├── urinorm.txt │ ├── test1-discover.txt │ ├── accept.txt │ └── test1-parsehtml.txt ├── test_urinorm.rb ├── util.rb ├── test_extension.rb ├── test_xri.rb ├── test_kvpost.rb ├── test_responses.rb ├── test_parsehtml.rb ├── test_nonce.rb ├── test_dh.rb ├── test_xrires.rb ├── test_ui.rb ├── test_linkparse.rb ├── test_cryptutil.rb ├── testutil.rb ├── test_trustroot.rb └── test_util.rb ├── NOTICE ├── Gemfile ├── contrib └── google │ ├── ruby-openid-apps-discovery-1.0.gem │ └── ruby-openid-apps-discovery-1.01.gem ├── .gitignore ├── Rakefile ├── .travis.yml ├── ruby-openid.gemspec ├── INSTALL.md ├── CHANGES-2.0.0 ├── CHANGES-2.1.0 └── README.md /admin/library-name: -------------------------------------------------------------------------------- 1 | ruby-openid -------------------------------------------------------------------------------- /examples/rails_openid/log/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/rails_openid/app/mailers/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/rails_openid/app/models/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/rails_openid/lib/assets/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/rails_openid/lib/tasks/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/rails_openid/public/favicon.ico: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/rails_openid/test/unit/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lib/ruby-openid.rb: -------------------------------------------------------------------------------- 1 | require 'openid' 2 | -------------------------------------------------------------------------------- /examples/rails_openid/db/development.sqlite3: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/rails_openid/test/fixtures/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/rails_openid/test/functional/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/rails_openid/test/integration/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lib/openid/version.rb: -------------------------------------------------------------------------------- 1 | module OpenID 2 | VERSION = "2.9.2" 3 | end 4 | -------------------------------------------------------------------------------- /examples/rails_openid/app/helpers/login_helper.rb: -------------------------------------------------------------------------------- 1 | module LoginHelper 2 | end 3 | -------------------------------------------------------------------------------- /examples/rails_openid/config/initializers/rails_root.rb: -------------------------------------------------------------------------------- 1 | ::RAILS_ROOT = Rails.root 2 | -------------------------------------------------------------------------------- /test/data/test_xrds/not-xrds.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /examples/rails_openid/app/helpers/application_helper.rb: -------------------------------------------------------------------------------- 1 | module ApplicationHelper 2 | end 3 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | This product includes software developed by JanRain, 2 | available from http://github.com/openid/ruby-openid 3 | -------------------------------------------------------------------------------- /examples/active_record_openid_store/lib/nonce.rb: -------------------------------------------------------------------------------- 1 | class Nonce < ActiveRecord::Base 2 | set_table_name 'open_id_nonces' 3 | end 4 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | # Specify your gem's dependencies in ruby-openid.gemspec 4 | gemspec 5 | 6 | gem 'rake' 7 | -------------------------------------------------------------------------------- /contrib/google/ruby-openid-apps-discovery-1.0.gem: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openid/ruby-openid/HEAD/contrib/google/ruby-openid-apps-discovery-1.0.gem -------------------------------------------------------------------------------- /examples/rails_openid/app/assets/images/rails.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openid/ruby-openid/HEAD/examples/rails_openid/app/assets/images/rails.png -------------------------------------------------------------------------------- /contrib/google/ruby-openid-apps-discovery-1.01.gem: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openid/ruby-openid/HEAD/contrib/google/ruby-openid-apps-discovery-1.01.gem -------------------------------------------------------------------------------- /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/rails_openid/app/controllers/application_controller.rb: -------------------------------------------------------------------------------- 1 | class ApplicationController < ActionController::Base 2 | protect_from_forgery 3 | end 4 | -------------------------------------------------------------------------------- /examples/rails_openid/public/images/openid_login_bg.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openid/ruby-openid/HEAD/examples/rails_openid/public/images/openid_login_bg.gif -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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/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/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/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 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /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/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/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/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/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 | -------------------------------------------------------------------------------- /test/data/test_xrds/no-xrd.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /admin/fixperms: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | cat < :test 15 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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 -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /test/data/test_xrds/status222.xrds: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | *x 5 | The subsegment does not exist 6 | 2006-08-18T00:02:35.000Z 7 | xri://= 8 | 9 | -------------------------------------------------------------------------------- /test/data/test_discover/yadis_no_delegate.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | http://openid.net/signon/1.0 8 | http://www.myopenid.com/server 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /test/data/test_discover/openid_no_delegate.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Identity Page for Smoker 5 | 6 | 7 | 8 |

foo

9 | 10 | 11 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /test/data/test_discover/openid2_xrds_no_local_id.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | http://specs.openid.net/auth/2.0/signon 8 | http://www.myopenid.com/server 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /test/data/test_discover/yadis_0entries.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | http://is-not-openid.unittest/ 9 | http://noffing.unittest./ 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /test/data/test_discover/openid2_xrds.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | http://specs.openid.net/auth/2.0/signon 8 | http://www.myopenid.com/server 9 | http://smoker.myopenid.com/ 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /test/data/test_discover/yadis_idp.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | http://specs.openid.net/auth/2.0/server 9 | http://www.myopenid.com/server 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /test/data/test_discover/openid.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Identity Page for Smoker 5 | 6 | 7 | 8 | 9 |

foo

10 | 11 | 12 | -------------------------------------------------------------------------------- /test/data/test_discover/openid2.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Identity Page for Smoker 5 | 6 | 7 | 8 | 9 |

foo

10 | 11 | 12 | -------------------------------------------------------------------------------- /test/data/test_discover/openid_utf8.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Identity Page for Smoker 5 | 6 | 7 | 8 | 9 |

こんにちは

10 | 11 | 12 | -------------------------------------------------------------------------------- /admin/prepare-release: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # 3 | # Prepare this repository for release 4 | # 5 | # required tools: 6 | # rdoc 7 | # darcs 8 | 9 | set -e 10 | 11 | HERE=$(readlink --canonicalize $(dirname "$0")) 12 | ROOT=$(dirname "$HERE") 13 | 14 | cd "$ROOT" 15 | 16 | # set permissions 17 | bash ./admin/fixperms 18 | 19 | # build documentation 20 | ./admin/build-docs 21 | 22 | # build changelog 23 | darcs changes --from-tag . --summary > CHANGELOG 24 | 25 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /test/data/test_discover/openid_1_and_2.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Identity Page for Smoker 5 | 6 | 7 | 8 | 9 |

foo

10 | 11 | 12 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /test/data/test_discover/yadis_idp_delegate.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | http://specs.openid.net/auth/2.0/server 9 | http://www.myopenid.com/server 10 | http://smoker.myopenid.com/ 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /test/data/test_discover/yadis_another_delegate.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | http://openid.net/signon/1.0 10 | http://vroom.unittest/server 11 | http://smoker.myopenid.com/ 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /admin/mkassoc: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require "openid/consumer/associationmanager" 4 | require "openid/store/memory" 5 | 6 | store = OpenID::Store::Memory.new 7 | ARGV.each do |server_url| 8 | unless URI::regexp =~ server_url 9 | puts "`#{server_url}` will be skipped for invalid URI format." 10 | next 11 | end 12 | 13 | mgr = OpenID::Consumer::AssociationManager.new(store, server_url) 14 | puts '=' * 50 15 | puts "Server: #{server_url}" 16 | puts mgr.get_association.serialize 17 | puts '-' * 50 18 | end 19 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /test/data/test_discover/openid_and_yadis.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Identity Page for Smoker 5 | 6 | 7 | 8 | 9 | 10 |

foo

11 | 12 | 13 | -------------------------------------------------------------------------------- /test/data/test_discover/malformed_meta_tag.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | <link rel="openid.server" 6 | href="http://www.myopenid.com/server" /> 7 | <link rel="openid.delegate" 8 | href="http://user.myopenid.com/" /> 9 | <link rel="openid2.local_id" 10 | href="http://user.myopenid.com/" /> 11 | <link rel="openid2.provider" 12 | href="http://www.myopenid.com/server" /> 13 | <meta http-equiv="X-XRDS-Location" 14 | http://www.myopenid.com/xrds?username=user.myopenid.com" /> 15 | 16 | </head> 17 | <body> 18 | </body> 19 | </html> 20 | -------------------------------------------------------------------------------- /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/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.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 -------------------------------------------------------------------------------- /test/data/test_discover/yadis_2_bad_local_id.xml: -------------------------------------------------------------------------------- 1 | <?xml version="1.0" encoding="UTF-8"?> 2 | <xrds:XRDS xmlns:xrds="xri://$xrds" 3 | xmlns="xri://$xrd*($v*2.0)" 4 | xmlns:openid="http://openid.net/xmlns/1.0" 5 | > 6 | <XRD> 7 | 8 | <Service priority="10"> 9 | <Type>http://specs.openid.net/auth/2.0/signon</Type> 10 | <URI>http://www.myopenid.com/server</URI> 11 | <LocalID>http://smoker.myopenid.com/</LocalID> 12 | <LocalID>http://localid.mismatch.invalid/</LocalID> 13 | </Service> 14 | </XRD> 15 | </xrds:XRDS> 16 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /test/data/test_xrds/README: -------------------------------------------------------------------------------- 1 | delegated-20060809.xrds - results from proxy.xri.net, determined by 2 | Drummond and Kevin to be incorrect. 3 | delegated-20060809-r1.xrds - Drummond's 1st correction 4 | delegated-20060809-r2.xrds - Drummond's 2nd correction 5 | 6 | spoofs: keturn's (=!E4)'s attempts to log in with Drummond's i-number (=!D2) 7 | spoof1.xrds 8 | spoof2.xrds 9 | spoof3.xrds - attempt to steal @!C0!D2 by having "at least one" CanonicalID 10 | match the $res service ProviderID. 11 | 12 | ref.xrds - resolving @ootao*test.ref, which refers to a neustar XRI. 13 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /test/data/test_discover/openid_1_and_2_xrds.xml: -------------------------------------------------------------------------------- 1 | <?xml version="1.0" encoding="UTF-8"?> 2 | <xrds:XRDS xmlns:xrds="xri://$xrds" 3 | xmlns="xri://$xrd*($v*2.0)" 4 | xmlns:openid="http://openid.net/xmlns/1.0" 5 | > 6 | <XRD> 7 | 8 | <Service priority="10"> 9 | <Type>http://specs.openid.net/auth/2.0/signon</Type> 10 | <Type>http://openid.net/signon/1.1</Type> 11 | <URI>http://www.myopenid.com/server</URI> 12 | <LocalID>http://smoker.myopenid.com/</LocalID> 13 | <openid:Delegate>http://smoker.myopenid.com/</openid:Delegate> 14 | </Service> 15 | </XRD> 16 | </xrds:XRDS> 17 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /test/data/test_discover/openid_1_and_2_xrds_bad_delegate.xml: -------------------------------------------------------------------------------- 1 | <?xml version="1.0" encoding="UTF-8"?> 2 | <xrds:XRDS xmlns:xrds="xri://$xrds" 3 | xmlns="xri://$xrd*($v*2.0)" 4 | xmlns:openid="http://openid.net/xmlns/1.0" 5 | > 6 | <XRD> 7 | 8 | <Service priority="10"> 9 | <Type>http://specs.openid.net/auth/2.0/signon</Type> 10 | <Type>http://openid.net/signon/1.0</Type> 11 | <Type>http://openid.net/signon/1.1</Type> 12 | <URI>http://www.myopenid.com/server</URI> 13 | <LocalID>http://smoker.myopenid.com/</LocalID> 14 | <openid:Delegate>http://localid.mismatch.invalid/</openid:Delegate> 15 | </Service> 16 | </XRD> 17 | </xrds:XRDS> 18 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /test/data/test_discover/yadis_2entries_idp.xml: -------------------------------------------------------------------------------- 1 | <?xml version="1.0" encoding="UTF-8"?> 2 | <xrds:XRDS xmlns:xrds="xri://$xrds" 3 | xmlns="xri://$xrd*($v*2.0)" 4 | xmlns:openid="http://openid.net/xmlns/1.0" 5 | > 6 | <XRD> 7 | <CanonicalID>=!1000</CanonicalID> 8 | 9 | <Service priority="10"> 10 | <Type>http://specs.openid.net/auth/2.0/signon</Type> 11 | <URI>http://www.myopenid.com/server</URI> 12 | <openid:LocalID>http://smoker.myopenid.com/</openid:LocalID> 13 | </Service> 14 | 15 | <Service priority="20"> 16 | <Type>http://specs.openid.net/auth/2.0/server</Type> 17 | <URI>http://www.livejournal.com/openid/server.bml</URI> 18 | </Service> 19 | 20 | </XRD> 21 | </xrds:XRDS> 22 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /examples/rails_openid/public/500.html: -------------------------------------------------------------------------------- 1 | <!DOCTYPE html> 2 | <html> 3 | <head> 4 | <title>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 | -------------------------------------------------------------------------------- /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 < 2 | 6 | 7 | =!1000 8 | 9 | 10 | http://openid.net/signon/1.0 11 | http://www.myopenid.com/server 12 | http://smoker.myopenid.com/ 13 | 14 | 15 | 16 | http://openid.net/signon/1.0 17 | http://www.livejournal.com/openid/server.bml 18 | http://frank.livejournal.com/ 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /test/data/test_xrds/spoof1.xrds: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | *keturn 5 | xri://= 6 | !E4 7 | =!E4 8 | 9 | 10 | xri://$res*auth*($v*2.0) 11 | http://keturn.example.com/resolve/ 12 | =!E4 13 | 14 | 15 | 16 | *isDrummond 17 | =!E4 18 | !D2 19 | =!D2 20 | 21 | http://openid.net/signon/1.0 22 | http://keturn.example.com/openid 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /test/data/test_xrds/spoof2.xrds: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | *keturn 5 | xri://= 6 | !E4 7 | =!E4 8 | 9 | 10 | xri://$res*auth*($v*2.0) 11 | http://keturn.example.com/resolve/ 12 | xri://= 13 | 14 | 15 | 16 | *isDrummond 17 | xri://= 18 | !D2 19 | =!D2 20 | 21 | http://openid.net/signon/1.0 22 | http://keturn.example.com/openid 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /test/test_urinorm.rb: -------------------------------------------------------------------------------- 1 | require "minitest/autorun" 2 | require "testutil" 3 | require "openid/urinorm" 4 | 5 | class URINormTestCase < Minitest::Test 6 | include OpenID::TestDataMixin 7 | 8 | def test_normalize 9 | lines = read_data_file('urinorm.txt') 10 | 11 | while lines.length > 0 12 | 13 | case_name = lines.shift.strip 14 | actual = lines.shift.strip 15 | expected = lines.shift.strip 16 | lines.shift #=> newline 17 | 18 | if expected == 'fail' 19 | begin 20 | OpenID::URINorm.urinorm(actual) 21 | rescue URI::InvalidURIError 22 | assert true 23 | else 24 | raise 'Should have gotten URI error' 25 | end 26 | else 27 | normalized = OpenID::URINorm.urinorm(actual) 28 | assert_equal(expected, normalized, case_name) 29 | end 30 | end 31 | end 32 | 33 | end 34 | 35 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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/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/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 | -------------------------------------------------------------------------------- /test/data/test_xrds/=j3h.2007.11.14.xrds: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | *j3h 5 | 6 | 2007-11-15T01:35:07.000Z 7 | xri://= 8 | !378C.2F61.25D6.F7EB 9 | =!378C.2F61.25D6.F7EB 10 | 11 | http://openid.net/signon/1.0 12 | 13 | http://2idi.com/openid/ 14 | https://2idi.com/openid/ 15 | 16 | 17 | xri://+i-service*(+contact)*($v*1.0) 18 | 19 | 20 | (+contact) 21 | 22 | http://2idi.com/contact/ 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /test/data/test_xrds/spoof3.xrds: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | *keturn 5 | xri://@ 6 | @E4 7 | @!E4 8 | 9 | 10 | xri://$res*auth*($v*2.0) 11 | http://keturn.example.com/resolve/ 12 | @!E4 13 | 14 | 15 | 16 | *is 17 | @!E4 18 | !D2 19 | =!C0 20 | =!E4!01 21 | 22 | xri://$res*auth*($v*2.0) 23 | http://keturn.example.com/resolve/ 24 | @!C0 25 | 26 | 27 | 28 | *drummond 29 | @!C0 30 | !D2 31 | @!C0!D2 32 | 33 | http://openid.net/signon/1.0 34 | http://keturn.example.com/openid 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /admin/gettlds.py: -------------------------------------------------------------------------------- 1 | """ 2 | Fetch the current TLD list from the IANA Web site, parse it, and print 3 | an expression suitable for direct insertion into each library's trust 4 | root validation module 5 | 6 | Usage: 7 | python gettlds.py (php|python|ruby) 8 | 9 | Then cut-n-paste. 10 | """ 11 | 12 | import urllib2 13 | 14 | import sys 15 | 16 | langs = { 17 | 'php': (r"'/\.(", 18 | "'", "|", "|' .", 19 | r")\.?$/'"), 20 | 'python': ("['", 21 | "'", "', '", "',", 22 | "']"), 23 | 'ruby': ("%w'", 24 | "", " ", "", 25 | "'"), 26 | } 27 | 28 | lang = sys.argv[1] 29 | prefix, line_prefix, separator, line_suffix, suffix = langs[lang] 30 | 31 | f = urllib2.urlopen('http://data.iana.org/TLD/tlds-alpha-by-domain.txt') 32 | tlds = [] 33 | output_line = "" 34 | for input_line in f: 35 | if input_line.startswith('#'): 36 | continue 37 | 38 | tld = input_line.strip().lower() 39 | new_output_line = output_line + prefix + tld 40 | if len(new_output_line) > 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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /test/data/test_xrds/delegated-20060809.xrds: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | *ootao 5 | 6 | 2006-08-09T22:07:13.000Z 7 | xri://@ 8 | !5BAD.2AA.3C72.AF46 9 | @!5BAD.2AA.3C72.AF46 10 | 11 | xri://$res*auth*($v*2.0) 12 | 13 | application/xrds+xml;trust=none 14 | http://resolve.ezibroker.net/resolve/@ootao/ 15 | 16 | 17 | http://openid.net/signon/1.0 18 | 19 | https://linksafe.ezibroker.net/server/ 20 | 21 | 22 | 23 | *test1 24 | SUCCESS 25 | xri://!!1003 26 | !0000.0000.3B9A.CA01 27 | @!5BAD.2AA.3C72.AF46!0000.0000.3B9A.CA01 28 | 29 | http://openid.net/signon/1.0 30 | 31 | https://linksafe.ezibroker.net/server/ 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /test/data/test_xrds/valid-populated-xrds.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | 10 | http://openid.net/signon/1.0 11 | http://www.myopenid.com/server 12 | http://josh.myopenid.com/ 13 | 14 | 15 | 16 | http://lid.netmesh.org/sso/2.0b5 17 | http://lid.netmesh.org/2.0b5 18 | http://mylid.net/josh 19 | 20 | 21 | 22 | http://openid.net/signon/1.0 23 | http://www.livejournal.com/openid/server.bml 24 | http://www.livejournal.com/users/nedthealpaca/ 25 | 26 | 27 | 28 | http://typekey.com/services/1.0 29 | joshhoyt 30 | 31 | 32 | 33 | http://openid.net/signon/1.0 34 | http://www.schtuff.com/openid 35 | http://users.schtuff.com/josh 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /test/data/test_xrds/delegated-20060809-r1.xrds: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | *ootao 5 | 6 | 2006-08-09T22:07:13.000Z 7 | xri://@ 8 | !5BAD.2AA.3C72.AF46 9 | @!5BAD.2AA.3C72.AF46 10 | 11 | xri://$res*auth*($v*2.0) 12 | xri://!!1003 13 | application/xrds+xml;trust=none 14 | http://resolve.ezibroker.net/resolve/@ootao/ 15 | 16 | 17 | http://openid.net/signon/1.0 18 | 19 | https://linksafe.ezibroker.net/server/ 20 | 21 | 22 | 23 | *test1 24 | SUCCESS 25 | xri://!!1003 26 | !0000.0000.3B9A.CA01 27 | @!5BAD.2AA.3C72.AF46!0000.0000.3B9A.CA01 28 | 29 | http://openid.net/signon/1.0 30 | 31 | https://linksafe.ezibroker.net/server/ 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /test/data/test_xrds/delegated-20060809-r2.xrds: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | *ootao 5 | 6 | 2006-08-09T22:07:13.000Z 7 | xri://@ 8 | !5BAD.2AA.3C72.AF46 9 | @!5BAD.2AA.3C72.AF46 10 | 11 | xri://$res*auth*($v*2.0) 12 | xri://@!5BAD.2AA.3C72.AF46 13 | application/xrds+xml;trust=none 14 | http://resolve.ezibroker.net/resolve/@ootao/ 15 | 16 | 17 | http://openid.net/signon/1.0 18 | 19 | https://linksafe.ezibroker.net/server/ 20 | 21 | 22 | 23 | *test1 24 | SUCCESS 25 | xri://@!5BAD.2AA.3C72.AF46 26 | !0000.0000.3B9A.CA01 27 | @!5BAD.2AA.3C72.AF46!0000.0000.3B9A.CA01 28 | 29 | http://openid.net/signon/1.0 30 | 31 | https://linksafe.ezibroker.net/server/ 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /test/data/test_xrds/prefixsometimes.xrds: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | *ootao 5 | 6 | 2006-08-09T22:07:13.000Z 7 | xri://@ 8 | !5BAD.2AA.3C72.AF46 9 | @!5BAD.2AA.3C72.AF46 10 | 11 | xri://$res*auth*($v*2.0) 12 | xri://@!5BAD.2AA.3C72.AF46 13 | application/xrds+xml;trust=none 14 | http://resolve.ezibroker.net/resolve/@ootao/ 15 | 16 | 17 | http://openid.net/signon/1.0 18 | 19 | https://linksafe.ezibroker.net/server/ 20 | 21 | 22 | 23 | *test1 24 | SUCCESS 25 | xri://@!5BAD.2AA.3C72.AF46 26 | !0000.0000.3B9A.CA01 27 | xri://@!5BAD.2AA.3C72.AF46!0000.0000.3B9A.CA01 28 | 29 | http://openid.net/signon/1.0 30 | 31 | https://linksafe.ezibroker.net/server/ 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /test/data/test_xrds/sometimesprefix.xrds: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | *ootao 5 | 6 | 2006-08-09T22:07:13.000Z 7 | xri://@ 8 | !5BAD.2AA.3C72.AF46 9 | xri://@!5BAD.2AA.3C72.AF46 10 | 11 | xri://$res*auth*($v*2.0) 12 | xri://@!5BAD.2AA.3C72.AF46 13 | application/xrds+xml;trust=none 14 | http://resolve.ezibroker.net/resolve/@ootao/ 15 | 16 | 17 | http://openid.net/signon/1.0 18 | 19 | https://linksafe.ezibroker.net/server/ 20 | 21 | 22 | 23 | *test1 24 | SUCCESS 25 | xri://@!5BAD.2AA.3C72.AF46 26 | !0000.0000.3B9A.CA01 27 | @!5BAD.2AA.3C72.AF46!0000.0000.3B9A.CA01 28 | 29 | http://openid.net/signon/1.0 30 | 31 | https://linksafe.ezibroker.net/server/ 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /test/util.rb: -------------------------------------------------------------------------------- 1 | # Utilities that are only used in the testing code 2 | require 'stringio' 3 | 4 | module OpenID 5 | module TestUtil 6 | def assert_log_matches(*regexes) 7 | begin 8 | old_logger = Util.logger 9 | log_output = StringIO.new 10 | Util.logger = Logger.new(log_output) 11 | result = yield 12 | ensure 13 | Util.logger = old_logger 14 | end 15 | log_output.rewind 16 | log_lines = log_output.readlines 17 | assert_equal(regexes.length, log_lines.length, 18 | [regexes, log_lines].inspect) 19 | log_lines.zip(regexes) do |line, regex| 20 | assert_match(regex, line) 21 | end 22 | result 23 | end 24 | 25 | def assert_log_line_count(num_lines) 26 | begin 27 | old_logger = Util.logger 28 | log_output = StringIO.new 29 | Util.logger = Logger.new(log_output) 30 | result = yield 31 | ensure 32 | Util.logger = old_logger 33 | end 34 | log_output.rewind 35 | log_lines = log_output.readlines 36 | assert_equal(num_lines, log_lines.length) 37 | result 38 | end 39 | 40 | def silence_logging 41 | begin 42 | old_logger = Util.logger 43 | log_output = StringIO.new 44 | Util.logger = Logger.new(log_output) 45 | result = yield 46 | ensure 47 | Util.logger = old_logger 48 | end 49 | result 50 | end 51 | end 52 | end 53 | 54 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /test/test_extension.rb: -------------------------------------------------------------------------------- 1 | require 'minitest/autorun' 2 | require 'openid/extension' 3 | require 'openid/message' 4 | 5 | module OpenID 6 | class DummyExtension < OpenID::Extension 7 | TEST_URI = 'http://an.extension' 8 | TEST_ALIAS = 'dummy' 9 | def initialize 10 | @ns_uri = TEST_URI 11 | @ns_alias = TEST_ALIAS 12 | end 13 | 14 | def get_extension_args 15 | return {} 16 | end 17 | end 18 | 19 | class ToMessageTest < Minitest::Test 20 | def test_OpenID1 21 | oid1_msg = Message.new(OPENID1_NS) 22 | ext = DummyExtension.new 23 | ext.to_message(oid1_msg) 24 | namespaces = oid1_msg.namespaces 25 | assert(namespaces.implicit?(DummyExtension::TEST_URI)) 26 | assert_equal( 27 | DummyExtension::TEST_URI, 28 | namespaces.get_namespace_uri(DummyExtension::TEST_ALIAS)) 29 | assert_equal(DummyExtension::TEST_ALIAS, 30 | namespaces.get_alias(DummyExtension::TEST_URI)) 31 | end 32 | 33 | def test_OpenID2 34 | oid2_msg = Message.new(OPENID2_NS) 35 | ext = DummyExtension.new 36 | ext.to_message(oid2_msg) 37 | namespaces = oid2_msg.namespaces 38 | assert(!namespaces.implicit?(DummyExtension::TEST_URI)) 39 | assert_equal( 40 | DummyExtension::TEST_URI, 41 | namespaces.get_namespace_uri(DummyExtension::TEST_ALIAS)) 42 | assert_equal(DummyExtension::TEST_ALIAS, 43 | namespaces.get_alias(DummyExtension::TEST_URI)) 44 | end 45 | end 46 | end 47 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /test/test_xri.rb: -------------------------------------------------------------------------------- 1 | require 'minitest/autorun' 2 | require 'openid/yadis/xri' 3 | 4 | module OpenID 5 | 6 | module Yadis 7 | 8 | class XriDiscoveryTestCase < Minitest::Test 9 | 10 | def test_isXRI? 11 | assert_equal(:xri, XRI.identifier_scheme('=john.smith')) 12 | assert_equal(:xri, XRI.identifier_scheme('@smiths/john')) 13 | assert_equal(:xri, XRI.identifier_scheme('xri://=john')) 14 | assert_equal(:xri, XRI.identifier_scheme('@ootao*test1')) 15 | assert_equal(:uri, XRI.identifier_scheme('smoker.myopenid.com')) 16 | assert_equal(:uri, XRI.identifier_scheme('http://smoker.myopenid.com')) 17 | assert_equal(:uri, XRI.identifier_scheme('https://smoker.myopenid.com')) 18 | end 19 | end 20 | 21 | class XriEscapingTestCase < Minitest::Test 22 | def test_escaping_percents 23 | assert_equal('@example/abc%252Fd/ef', 24 | XRI.escape_for_iri('@example/abc%2Fd/ef')) 25 | end 26 | 27 | def test_escaping_xref 28 | # no escapes 29 | assert_equal('@example/foo/(@bar)', 30 | XRI.escape_for_iri('@example/foo/(@bar)')) 31 | # escape slashes 32 | assert_equal('@example/foo/(@bar%2Fbaz)', 33 | XRI.escape_for_iri('@example/foo/(@bar/baz)')) 34 | # escape query ? and fragment # 35 | assert_equal('@example/foo/(@baz%3Fp=q%23r)?i=j#k', 36 | XRI.escape_for_iri('@example/foo/(@baz?p=q#r)?i=j#k')) 37 | end 38 | end 39 | 40 | class XriTransformationTestCase < Minitest::Test 41 | def test_to_iri_normal 42 | assert_equal('xri://@example', XRI.to_iri_normal('@example')) 43 | end 44 | # iri_to_url: 45 | # various ucschar to hex 46 | end 47 | end 48 | end 49 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /test/data/urinorm.txt: -------------------------------------------------------------------------------- 1 | Already normal form 2 | http://example.com/ 3 | http://example.com/ 4 | 5 | Add a trailing slash 6 | http://example.com 7 | http://example.com/ 8 | 9 | Remove an empty port segment 10 | http://example.com:/ 11 | http://example.com/ 12 | 13 | Remove a default port segment 14 | http://example.com:80/ 15 | http://example.com/ 16 | 17 | Capitalization in host names 18 | http://wWw.exaMPLE.COm/ 19 | http://www.example.com/ 20 | 21 | Capitalization in scheme names 22 | htTP://example.com/ 23 | http://example.com/ 24 | 25 | Capitalization in percent-escaped reserved characters 26 | http://example.com/foo%2cbar 27 | http://example.com/foo%2Cbar 28 | 29 | Unescape percent-encoded unreserved characters 30 | http://example.com/foo%2Dbar%2dbaz 31 | http://example.com/foo-bar-baz 32 | 33 | remove_dot_segments example 1 34 | http://example.com/a/b/c/./../../g 35 | http://example.com/a/g 36 | 37 | remove_dot_segments example 2 38 | http://example.com/mid/content=5/../6 39 | http://example.com/mid/6 40 | 41 | remove_dot_segments: single-dot 42 | http://example.com/a/./b 43 | http://example.com/a/b 44 | 45 | remove_dot_segments: double-dot 46 | http://example.com/a/../b 47 | http://example.com/b 48 | 49 | remove_dot_segments: leading double-dot 50 | http://example.com/../b 51 | http://example.com/b 52 | 53 | remove_dot_segments: trailing single-dot 54 | http://example.com/a/. 55 | http://example.com/a/ 56 | 57 | remove_dot_segments: trailing double-dot 58 | http://example.com/a/.. 59 | http://example.com/ 60 | 61 | remove_dot_segments: trailing single-dot-slash 62 | http://example.com/a/./ 63 | http://example.com/a/ 64 | 65 | remove_dot_segments: trailing double-dot-slash 66 | http://example.com/a/../ 67 | http://example.com/ 68 | 69 | Test of all kinds of syntax-based normalization 70 | hTTPS://a/./b/../b/%63/%7bfoo%7d 71 | https://a/b/c/%7Bfoo%7D 72 | 73 | Unsupported scheme 74 | ftp://example.com/ 75 | fail 76 | 77 | Non-absolute URI 78 | http:/foo 79 | fail -------------------------------------------------------------------------------- /test/test_kvpost.rb: -------------------------------------------------------------------------------- 1 | require "minitest/autorun" 2 | require "testutil" 3 | require "openid/kvpost" 4 | require "openid/kvform" 5 | require "openid/message" 6 | 7 | module OpenID 8 | class KVPostTestCase < Minitest::Test 9 | include FetcherMixin 10 | 11 | def mk_resp(status, resp_hash) 12 | return MockResponse.new(status, Util.dict_to_kv(resp_hash)) 13 | end 14 | 15 | def test_msg_from_http_resp_success 16 | resp = mk_resp(200, {'mode' => 'seitan'}) 17 | msg = Message.from_http_response(resp, 'http://invalid/') 18 | assert_equal({'openid.mode' => 'seitan'}, msg.to_post_args) 19 | end 20 | 21 | def test_400 22 | args = {'error' => 'I ate too much cheese', 23 | 'error_code' => 'sadness'} 24 | resp = mk_resp(400, args) 25 | begin 26 | val = Message.from_http_response(resp, 'http://invalid/') 27 | rescue ServerError => why 28 | assert_equal(why.error_text, 'I ate too much cheese') 29 | assert_equal(why.error_code, 'sadness') 30 | assert_equal(why.message.to_args, args) 31 | else 32 | fail("Expected exception. Got: #{val}") 33 | end 34 | end 35 | 36 | def test_500 37 | args = {'error' => 'I ate too much cheese', 38 | 'error_code' => 'sadness'} 39 | resp = mk_resp(500, args) 40 | assert_raises(HTTPStatusError) { 41 | Message.from_http_response(resp, 'http://invalid') 42 | } 43 | end 44 | 45 | def make_kv_post_with_response(status, args) 46 | resp = mk_resp(status, args) 47 | mock_fetcher = Class.new do 48 | define_method(:fetch) do |url, body, xxx, yyy| 49 | resp 50 | end 51 | end 52 | 53 | with_fetcher(mock_fetcher.new) do 54 | OpenID.make_kv_post(Message.from_openid_args(args), 'http://invalid/') 55 | end 56 | end 57 | 58 | def test_make_kv_post 59 | assert_raises(HTTPStatusError) { 60 | make_kv_post_with_response(500, {}) 61 | } 62 | end 63 | end 64 | end 65 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /test/test_responses.rb: -------------------------------------------------------------------------------- 1 | require "minitest/autorun" 2 | require "openid/consumer/discovery" 3 | require "openid/consumer/responses" 4 | 5 | module OpenID 6 | class Consumer 7 | module TestResponses 8 | class TestSuccessResponse < Minitest::Test 9 | def setup 10 | @endpoint = OpenIDServiceEndpoint.new 11 | @endpoint.claimed_id = 'identity_url' 12 | end 13 | 14 | def test_extension_response 15 | q = { 16 | 'ns.sreg' => 'urn:sreg', 17 | 'ns.unittest' => 'urn:unittest', 18 | 'unittest.one' => '1', 19 | 'unittest.two' => '2', 20 | 'sreg.nickname' => 'j3h', 21 | 'return_to' => 'return_to', 22 | } 23 | signed_list = q.keys.map { |k| 'openid.' + k } 24 | msg = Message.from_openid_args(q) 25 | resp = SuccessResponse.new(@endpoint, msg, signed_list) 26 | utargs = resp.extension_response('urn:unittest', false) 27 | assert_equal(utargs, {'one' => '1', 'two' => '2'}) 28 | sregargs = resp.extension_response('urn:sreg', false) 29 | assert_equal(sregargs, {'nickname' => 'j3h'}) 30 | end 31 | 32 | def test_extension_response_signed 33 | args = { 34 | 'ns.sreg' => 'urn:sreg', 35 | 'ns.unittest' => 'urn:unittest', 36 | 'unittest.one' => '1', 37 | 'unittest.two' => '2', 38 | 'sreg.nickname' => 'j3h', 39 | 'sreg.dob' => 'yesterday', 40 | 'return_to' => 'return_to', 41 | 'signed' => 'sreg.nickname,unittest.one,sreg.dob', 42 | } 43 | 44 | signed_list = ['openid.sreg.nickname', 45 | 'openid.unittest.one', 46 | 'openid.sreg.dob',] 47 | 48 | msg = Message.from_openid_args(args) 49 | resp = SuccessResponse.new(@endpoint, msg, signed_list) 50 | 51 | # All args in this NS are signed, so expect all. 52 | sregargs = resp.extension_response('urn:sreg', true) 53 | assert_equal(sregargs, {'nickname' => 'j3h', 'dob' => 'yesterday'}) 54 | 55 | # Not all args in this NS are signed, so expect nil when 56 | # asking for them. 57 | utargs = resp.extension_response('urn:unittest', true) 58 | assert_nil(utargs) 59 | end 60 | end 61 | end 62 | end 63 | end 64 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /test/test_parsehtml.rb: -------------------------------------------------------------------------------- 1 | require "minitest/autorun" 2 | require "testutil" 3 | require "openid/yadis/parsehtml" 4 | 5 | module OpenID 6 | class ParseHTMLTestCase < Minitest::Test 7 | include OpenID::TestDataMixin 8 | 9 | def test_parsehtml 10 | reserved_values = ['None', 'EOF'] 11 | chunks = read_data_file('test1-parsehtml.txt', false).split("\f\n") 12 | 13 | chunks.each{|c| 14 | expected, html = c.split("\n", 2) 15 | found = Yadis::html_yadis_location(html) 16 | 17 | assert(!reserved_values.member?(found)) 18 | 19 | # this case is a little hard to detect and the distinction 20 | # seems unimportant 21 | expected = "None" if expected == "EOF" 22 | 23 | found = "None" if found.nil? 24 | assert_equal(expected, found, html.split("\n",2)[0]) 25 | } 26 | end 27 | end 28 | 29 | # the HTML tokenizer test 30 | class TC_TestHTMLTokenizer < Minitest::Test 31 | def test_bad_link 32 | toke = HTMLTokenizer.new("

foo

") 33 | assert("http://bad.com/link" == toke.getTag("a").attr_hash['href']) 34 | end 35 | 36 | def test_namespace 37 | toke = HTMLTokenizer.new("") 38 | assert("http://www.com/foo" == toke.getTag("f:table").attr_hash['xmlns:f']) 39 | end 40 | 41 | def test_comment 42 | toke = HTMLTokenizer.new("") 43 | t = toke.getNextToken 44 | assert(HTMLComment == t.class) 45 | assert("comment on me" == t.contents) 46 | end 47 | 48 | def test_full 49 | page = " 50 | 51 | This is the title 52 | 53 | 56 | 57 |

This is the header

58 |

59 | This is the paragraph, it contains 60 | links, 61 | images
62 | are
63 | really cool. Ok, here is some more text and 64 | another link. 65 |

66 | 67 | 68 | " 69 | toke = HTMLTokenizer.new(page) 70 | 71 | assert("

" == toke.getTag("h1", "h2", "h3").to_s.downcase) 72 | assert(HTMLTag.new("") == toke.getTag("IMG", "A")) 73 | assert("links" == toke.getTrimmedText) 74 | assert(toke.getTag("IMG", "A").attr_hash['optional']) 75 | assert("_blank" == toke.getTag("IMG", "A").attr_hash['target']) 76 | end 77 | end 78 | end 79 | 80 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /test/data/test_xrds/subsegments.xrds: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | *nishitani 5 | 6 | 2007-12-25T11:33:39.000Z 7 | xri://= 8 | !E117.EF2F.454B.C707 9 | =!E117.EF2F.454B.C707 10 | 11 | http://openid.net/signon/1.0 12 | xri://!!1003!103 13 | https://linksafe.ezibroker.net/server/ 14 | 15 | 16 | xri://$res*auth*($v*2.0) 17 | xri://!!1003!103 18 | application/xrds+xml;trust=none 19 | http://resolve.ezibroker.net/resolve/=nishitani/ 20 | 21 | 22 | xri://+i-service*(+forwarding)*($v*1.0) 23 | 24 | xri://!!1003!103 25 | (+index) 26 | 27 | http://linksafe-forward.ezibroker.net/forwarding/ 28 | 29 | 30 | 31 | *masaki 32 | SUCCESS 33 | xri://!!1003 34 | !0000.0000.3B9A.CA01 35 | =!E117.EF2F.454B.C707!0000.0000.3B9A.CA01 36 | 37 | http://openid.net/signon/1.0 38 | xri://!!1003!103 39 | https://linksafe.ezibroker.net/server/ 40 | 41 | 42 | xri://+i-service*(+contact)*($v*1.0) 43 | 44 | xri://!!1003!103 45 | (+contact) 46 | 47 | http://linksafe-contact.ezibroker.net/contact/ 48 | 49 | 50 | xri://+i-service*(+forwarding)*($v*1.0) 51 | 52 | xri://!!1003!103 53 | (+index) 54 | 55 | http://linksafe-forward.ezibroker.net/forwarding/ 56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /test/test_nonce.rb: -------------------------------------------------------------------------------- 1 | require 'minitest/autorun' 2 | require 'openid/store/nonce' 3 | 4 | module OpenID 5 | class NonceTestCase < Minitest::Test 6 | 7 | NONCE_RE = /\A\d{4}-\d\d-\d\dT\d\d:\d\d:\d\dZ/ 8 | 9 | def test_mk_nonce 10 | nonce = Nonce::mk_nonce 11 | assert(nonce.match(NONCE_RE)) 12 | assert(nonce.size == 26) 13 | end 14 | 15 | def test_mk_nonce_time 16 | nonce = Nonce::mk_nonce(0) 17 | assert(nonce.match(NONCE_RE)) 18 | assert(nonce.size == 26) 19 | assert(nonce.match(/^1970-01-01T00:00:00Z/)) 20 | end 21 | 22 | def test_split 23 | s = '1970-01-01T00:00:00Z' 24 | expected_t = 0 25 | expected_salt = '' 26 | actual_t, actual_salt = Nonce::split_nonce(s) 27 | assert_equal(expected_t, actual_t) 28 | assert_equal(expected_salt, actual_salt) 29 | end 30 | 31 | def test_mk_split 32 | t = 42 33 | nonce_str = Nonce::mk_nonce(t) 34 | assert(nonce_str.match(NONCE_RE)) 35 | at, salt = Nonce::split_nonce(nonce_str) 36 | assert_equal(6, salt.size) 37 | assert_equal(t, at) 38 | end 39 | 40 | def test_bad_split 41 | cases = [ 42 | '', 43 | '1970-01-01T00:00:00+1:00', 44 | '1969-01-01T00:00:00Z', 45 | '1970-00-01T00:00:00Z', 46 | '1970.01-01T00:00:00Z', 47 | 'Thu Sep 7 13:29:31 PDT 2006', 48 | 'monkeys', 49 | ] 50 | cases.each{|c| 51 | assert_raises(ArgumentError, c.inspect) { Nonce::split_nonce(c) } 52 | } 53 | end 54 | 55 | def test_check_timestamp 56 | cases = [ 57 | # exact, no allowed skew 58 | ['1970-01-01T00:00:00Z', 0, 0, true], 59 | 60 | # exact, large skew 61 | ['1970-01-01T00:00:00Z', 1000, 0, true], 62 | 63 | # no allowed skew, one second old 64 | ['1970-01-01T00:00:00Z', 0, 1, false], 65 | 66 | # many seconds old, outside of skew 67 | ['1970-01-01T00:00:00Z', 10, 50, false], 68 | 69 | # one second old, one second skew allowed 70 | ['1970-01-01T00:00:00Z', 1, 1, true], 71 | 72 | # One second in the future, one second skew allowed 73 | ['1970-01-01T00:00:02Z', 1, 1, true], 74 | 75 | # two seconds in the future, one second skew allowed 76 | ['1970-01-01T00:00:02Z', 1, 0, false], 77 | 78 | # malformed nonce string 79 | ['monkeys', 0, 0, false], 80 | ] 81 | 82 | cases.each{|c| 83 | (nonce_str, allowed_skew, now, expected) = c 84 | actual = Nonce::check_timestamp(nonce_str, allowed_skew, now) 85 | assert_equal(expected, actual, c.inspect) 86 | } 87 | end 88 | end 89 | end 90 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /test/test_dh.rb: -------------------------------------------------------------------------------- 1 | require 'minitest/autorun' 2 | require 'testutil' 3 | require 'openid/dh' 4 | 5 | module OpenID 6 | class DiffieHellmanExposed < OpenID::DiffieHellman 7 | def DiffieHellmanExposed.strxor_for_testing(a, b) 8 | return DiffieHellmanExposed.strxor(a, b) 9 | end 10 | end 11 | 12 | class DiffieHellmanTestCase < Minitest::Test 13 | include OpenID::TestDataMixin 14 | 15 | NUL = "\x00" 16 | 17 | def test_strxor_success 18 | [#input 1 input 2 expected 19 | [NUL, NUL, NUL ], 20 | ["\x01", NUL, "\x01" ], 21 | ["a", "a", NUL ], 22 | ["a", NUL, "a" ], 23 | ["abc", NUL * 3, "abc" ], 24 | ["x" * 10, NUL * 10, "x" * 10], 25 | ["\x01", "\x02", "\x03" ], 26 | ["\xf0", "\x0f", "\xff" ], 27 | ["\xff", "\x0f", "\xf0" ], 28 | ].each do |input1, input2, expected| 29 | actual = DiffieHellmanExposed.strxor_for_testing(input1, input2) 30 | assert_equal(expected.force_encoding("UTF-8"), actual.force_encoding("UTF-8")) 31 | end 32 | end 33 | 34 | def test_strxor_failure 35 | [ 36 | ['', 'a' ], 37 | ['foo', 'ba' ], 38 | [NUL * 3, NUL * 4], 39 | [255, 127 ].map{|h| (0..h).map{|i|i.chr}.join('')}, 40 | ].each do |aa, bb| 41 | assert_raises(ArgumentError) { 42 | DiffieHellmanExposed.strxor(aa, bb) 43 | } 44 | end 45 | end 46 | 47 | def test_simple_exchange 48 | dh1 = DiffieHellman.from_defaults() 49 | dh2 = DiffieHellman.from_defaults() 50 | secret1 = dh1.get_shared_secret(dh2.public) 51 | secret2 = dh2.get_shared_secret(dh1.public) 52 | assert_equal(secret1, secret2) 53 | end 54 | 55 | def test_xor_secret 56 | dh1 = DiffieHellman.from_defaults() 57 | dh2 = DiffieHellman.from_defaults() 58 | secret = "Shhhhhh! don't tell!" 59 | encrypted = dh1.xor_secret((CryptUtil.method :sha1), dh2.public, secret) 60 | decrypted = dh2.xor_secret((CryptUtil.method :sha1), dh1.public, encrypted) 61 | assert_equal(secret, decrypted) 62 | end 63 | 64 | def test_dh 65 | dh = DiffieHellman.from_defaults() 66 | class << dh 67 | def set_private_test(priv) 68 | set_private(priv) 69 | end 70 | end 71 | 72 | read_data_file('dh.txt', true).each do |line| 73 | priv, pub = line.split(' ').map {|x| x.to_i} 74 | dh.set_private_test(priv) 75 | assert_equal(dh.public, pub) 76 | end 77 | end 78 | 79 | def test_using_defaults 80 | dh = DiffieHellman.from_defaults() 81 | assert(dh.using_default_values?) 82 | dh = DiffieHellman.new(3, 2750161) 83 | assert(!dh.using_default_values?) 84 | end 85 | end 86 | end 87 | -------------------------------------------------------------------------------- /test/test_xrires.rb: -------------------------------------------------------------------------------- 1 | require 'minitest/autorun' 2 | require 'openid/yadis/xrires' 3 | 4 | module OpenID 5 | module Yadis 6 | 7 | class XRDSFetcher 8 | def initialize(results) 9 | @results = results 10 | end 11 | 12 | def fetch(url, body=nil, headers=nil, redirect_limit=nil) 13 | if !@results.empty? 14 | return @results.shift 15 | end 16 | 17 | nil 18 | end 19 | end 20 | 21 | class ProxyQueryTestCase < Minitest::Test 22 | def setup 23 | @proxy_url = 'http://xri.example.com/' 24 | @proxy = XRI::ProxyResolver.new(@proxy_url) 25 | @servicetype = 'xri://+i-service*(+forwarding)*($v*1.0)' 26 | @servicetype_enc = 'xri%3A%2F%2F%2Bi-service%2A%28%2Bforwarding%29%2A%28%24v%2A1.0%29' 27 | end 28 | 29 | def test_proxy_url 30 | st = @servicetype 31 | ste = @servicetype_enc 32 | args_esc = ["_xrd_r=application%2Fxrds%2Bxml", "_xrd_t=#{ste}"] 33 | pqu = @proxy.method('query_url') 34 | h = @proxy_url 35 | 36 | assert_match h + '=foo?', pqu.call('=foo', st) 37 | assert_match args_esc[0], pqu.call('=foo', st) 38 | assert_match args_esc[1], pqu.call('=foo', st) 39 | 40 | assert_match h + '=foo/bar?baz&', pqu.call('=foo/bar?baz', st) 41 | assert_match args_esc[0], pqu.call('=foo/bar?baz', st) 42 | assert_match args_esc[1], pqu.call('=foo/bar?baz', st) 43 | 44 | assert_match h + '=foo/bar?baz=quux&', pqu.call('=foo/bar?baz=quux', st) 45 | assert_match args_esc[0], pqu.call('=foo/bar?baz=quux', st) 46 | assert_match args_esc[1], pqu.call('=foo/bar?baz=quux', st) 47 | 48 | assert_match h + '=foo/bar?mi=fa&so=la&', pqu.call('=foo/bar?mi=fa&so=la', st) 49 | assert_match args_esc[0], pqu.call('=foo/bar?mi=fa&so=la', st) 50 | assert_match args_esc[1], pqu.call('=foo/bar?mi=fa&so=la', st) 51 | 52 | # With no service endpoint selection. 53 | args_esc = "_xrd_r=application%2Fxrds%2Bxml%3Bsep%3Dfalse" 54 | 55 | assert_match h + '=foo?', pqu.call('=foo', nil) 56 | assert_match args_esc, pqu.call('=foo', nil) 57 | end 58 | 59 | def test_proxy_url_qmarks 60 | st = @servicetype 61 | ste = @servicetype_enc 62 | args_esc = ["_xrd_r=application%2Fxrds%2Bxml", "_xrd_t=#{ste}"] 63 | pqu = @proxy.method('query_url') 64 | h = @proxy_url 65 | 66 | assert_match h + '=foo/bar??', pqu.call('=foo/bar?', st) 67 | assert_match args_esc[0], pqu.call('=foo/bar?', st) 68 | assert_match args_esc[1], pqu.call('=foo/bar?', st) 69 | 70 | assert_match h + '=foo/bar????', pqu.call('=foo/bar???', st) 71 | assert_match args_esc[0], pqu.call('=foo/bar???', st) 72 | assert_match args_esc[1], pqu.call('=foo/bar???', st) 73 | end 74 | end 75 | end 76 | end 77 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /test/test_ui.rb: -------------------------------------------------------------------------------- 1 | require 'openid/extensions/ui' 2 | require 'openid/message' 3 | require 'openid/server' 4 | require 'minitest/autorun' 5 | 6 | module OpenID 7 | module UITest 8 | class UIRequestTestCase < Minitest::Test 9 | 10 | def setup 11 | @req = UI::Request.new 12 | end 13 | 14 | def test_construct 15 | assert_nil @req.mode 16 | assert_nil @req.icon 17 | assert_nil @req.lang 18 | assert_equal 'ui', @req.ns_alias 19 | 20 | req2 = UI::Request.new("popup", true, "ja-JP") 21 | assert_equal "popup", req2.mode 22 | assert_equal true, req2.icon 23 | assert_equal "ja-JP", req2.lang 24 | end 25 | 26 | def test_add_mode 27 | @req.mode = "popup" 28 | assert_equal "popup", @req.mode 29 | end 30 | 31 | def test_add_icon 32 | @req.icon = true 33 | assert_equal true, @req.icon 34 | end 35 | 36 | def test_add_lang 37 | @req.lang = "ja-JP" 38 | assert_equal "ja-JP", @req.lang 39 | end 40 | 41 | def test_get_extension_args 42 | assert_equal({}, @req.get_extension_args) 43 | @req.mode = "popup" 44 | assert_equal({'mode' => 'popup'}, @req.get_extension_args) 45 | @req.icon = true 46 | assert_equal({'mode' => 'popup', 'icon' => true}, @req.get_extension_args) 47 | @req.lang = "ja-JP" 48 | assert_equal({'mode' => 'popup', 'icon' => true, 'lang' => 'ja-JP'}, @req.get_extension_args) 49 | end 50 | 51 | def test_parse_extension_args 52 | args = {'mode' => 'popup', 'icon' => true, 'lang' => 'ja-JP'} 53 | @req.parse_extension_args args 54 | assert_equal "popup", @req.mode 55 | assert_equal true, @req.icon 56 | assert_equal "ja-JP", @req.lang 57 | end 58 | 59 | def test_parse_extension_args_empty 60 | @req.parse_extension_args({}) 61 | assert_nil @req.mode 62 | assert_nil @req.icon 63 | assert_nil @req.lang 64 | end 65 | 66 | def test_from_openid_request 67 | openid_req_msg = Message.from_openid_args( 68 | 'mode' => 'checkid_setup', 69 | 'ns' => OPENID2_NS, 70 | 'ns.ui' => UI::NS_URI, 71 | 'ui.mode' => 'popup', 72 | 'ui.icon' => true, 73 | 'ui.lang' => 'ja-JP' 74 | ) 75 | oid_req = Server::OpenIDRequest.new 76 | oid_req.message = openid_req_msg 77 | req = UI::Request.from_openid_request oid_req 78 | assert_equal "popup", req.mode 79 | assert_equal true, req.icon 80 | assert_equal "ja-JP", req.lang 81 | end 82 | 83 | def test_from_openid_request_no_ui_params 84 | message = Message.new 85 | openid_req = Server::OpenIDRequest.new 86 | openid_req.message = message 87 | ui_req = UI::Request.from_openid_request openid_req 88 | assert ui_req.nil? 89 | end 90 | 91 | end 92 | end 93 | end 94 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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/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/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/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/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 | -------------------------------------------------------------------------------- /test/test_linkparse.rb: -------------------------------------------------------------------------------- 1 | require 'minitest/autorun' 2 | require 'testutil' 3 | require 'openid/consumer/html_parse' 4 | 5 | class LinkParseTestCase < Minitest::Test 6 | include OpenID::TestDataMixin 7 | 8 | def attr_cmp(expected, found) 9 | e = expected.to_a.sort 10 | f = found.to_a.sort 11 | while (ep = e.shift) 12 | ek, ev = ep 13 | fk, fv = f.shift 14 | ok = false 15 | while ek[-1] == '*'[0] # optional entry detected 16 | if fk == ek[0...-1] and fv==ev # optional entry found 17 | ok = true 18 | break 19 | else # not found. okay, move on to next expected pair 20 | ek, ev = e.shift 21 | end 22 | if ek.nil? 23 | if fk == nil 24 | ok = true 25 | end 26 | break 27 | end 28 | end 29 | next if ok 30 | next if fk == ek and fv == ev 31 | return false 32 | end 33 | return f.empty? 34 | end 35 | 36 | def test_attrcmp 37 | good = [ 38 | [{'foo' => 'bar'},{'foo' => 'bar'}], 39 | [{'foo*' => 'bar'},{'foo' => 'bar'}], 40 | [{'foo' => 'bar', 'bam*' => 'baz'},{'foo' => 'bar'}], 41 | [{'foo' => 'bar', 'bam*' => 'baz', 'tak' => 'tal'}, 42 | {'foo' => 'bar', 'tak' => 'tal'}], 43 | ] 44 | bad = [ 45 | [{},{'foo' => 'bar'}], 46 | [{'foo' => 'bar'}, {'bam' => 'baz'}], 47 | [{'foo' => 'bar'}, {}], 48 | [{'foo*' => 'bar'},{'foo*' => 'bar'}], 49 | [{'foo' => 'bar', 'tak' => 'tal'}, {'foo' => 'bar'}] 50 | ] 51 | good.each{|c|assert(attr_cmp(c[0],c[1]),c.inspect)} 52 | bad.each{|c|assert(!attr_cmp(c[0],c[1]),c.inspect)} 53 | 54 | end 55 | 56 | def test_linkparse 57 | cases = read_data_file('linkparse.txt', false).split("\n\n\n") 58 | 59 | numtests = nil 60 | testnum = 0 61 | cases.each {|c| 62 | headers, html = c.split("\n\n",2) 63 | expected_links = [] 64 | name = "" 65 | testnum += 1 66 | headers.split("\n").each{|h| 67 | k,v = h.split(":",2) 68 | v = '' if v.nil? 69 | if k == "Num Tests" 70 | assert(numtests.nil?, "datafile parsing error: there can be only one NumTests") 71 | numtests = v.to_i 72 | testnum = 0 73 | next 74 | elsif k == "Name" 75 | name = v.strip 76 | elsif k == "Link" or k == "Link*" 77 | attrs = {} 78 | v.strip.split.each{|a| 79 | kk,vv = a.split('=') 80 | attrs[kk]=vv 81 | } 82 | expected_links << [k== "Link*", attrs] 83 | else 84 | assert(false, "datafile parsing error: bad header #{h}") 85 | end 86 | } 87 | html = html.force_encoding('UTF-8') if html.respond_to? :force_encoding 88 | links = OpenID::parse_link_attrs(html) 89 | 90 | found = links.dup 91 | expected = expected_links.dup 92 | while(fl = found.shift) 93 | optional, el = expected.shift 94 | while optional and !attr_cmp(el, fl) and not expected.empty? 95 | optional, el = expected.shift 96 | end 97 | assert(attr_cmp(el,fl), "#{name}: #{fl.inspect} does not match #{el.inspect}") 98 | end 99 | } 100 | assert_equal(numtests, testnum, "Number of tests") 101 | 102 | # test handling of invalid UTF-8 byte sequences 103 | if "".respond_to? :force_encoding 104 | html = "hello joel\255".force_encoding('UTF-8') 105 | else 106 | html = "hello joel\255" 107 | end 108 | OpenID::parse_link_attrs(html) 109 | 110 | end 111 | end 112 | -------------------------------------------------------------------------------- /test/test_cryptutil.rb: -------------------------------------------------------------------------------- 1 | # coding: ASCII-8BIT 2 | require "minitest/autorun" 3 | require "openid/cryptutil" 4 | require "pathname" 5 | 6 | class CryptUtilTestCase < Minitest::Test 7 | BIG = 2 ** 256 8 | 9 | def test_rand 10 | # If this is not true, the rest of our test won't work 11 | assert(BIG.is_a?(Bignum)) 12 | 13 | # It's possible that these will be small enough for fixnums, but 14 | # extraorindarily unlikely. 15 | a = OpenID::CryptUtil.rand(BIG) 16 | b = OpenID::CryptUtil.rand(BIG) 17 | assert(a.is_a?(Bignum)) 18 | assert(b.is_a?(Bignum)) 19 | refute_equal(a, b) 20 | end 21 | 22 | def test_rand_doesnt_depend_on_srand 23 | Kernel.srand(1) 24 | a = OpenID::CryptUtil.rand(BIG) 25 | Kernel.srand(1) 26 | b = OpenID::CryptUtil.rand(BIG) 27 | refute_equal(a, b) 28 | end 29 | 30 | def test_random_binary_convert 31 | (0..500).each do 32 | n = (0..10).inject(0) {|sum, element| sum + OpenID::CryptUtil.rand(BIG) } 33 | s = OpenID::CryptUtil.num_to_binary n 34 | assert(s.is_a?(String)) 35 | n_converted_back = OpenID::CryptUtil.binary_to_num(s) 36 | assert_equal(n, n_converted_back) 37 | end 38 | end 39 | 40 | def test_enumerated_binary_convert 41 | { 42 | "\x00" => 0, 43 | "\x01" => 1, 44 | "\x7F" => 127, 45 | "\x00\xFF" => 255, 46 | "\x00\x80" => 128, 47 | "\x00\x81" => 129, 48 | "\x00\x80\x00" => 32768, 49 | "OpenID is cool" => 1611215304203901150134421257416556, 50 | }.each do |str, num| 51 | num_prime = OpenID::CryptUtil.binary_to_num(str) 52 | str_prime = OpenID::CryptUtil.num_to_binary(num) 53 | assert_equal(num, num_prime) 54 | assert_equal(str, str_prime) 55 | end 56 | end 57 | 58 | def with_n2b64 59 | test_dir = Pathname.new(__FILE__).dirname 60 | filename = test_dir.join('data', 'n2b64') 61 | File.open(filename) do |file| 62 | file.each_line do |line| 63 | base64, base10 = line.chomp.split 64 | yield base64, base10.to_i 65 | end 66 | end 67 | end 68 | 69 | def test_base64_to_num 70 | with_n2b64 do |base64, num| 71 | assert_equal(num, OpenID::CryptUtil.base64_to_num(base64)) 72 | end 73 | end 74 | 75 | def test_base64_to_num_invalid 76 | assert_raises(ArgumentError) { 77 | OpenID::CryptUtil.base64_to_num('!@#$') 78 | } 79 | end 80 | 81 | def test_num_to_base64 82 | with_n2b64 do |base64, num| 83 | assert_equal(base64, OpenID::CryptUtil.num_to_base64(num)) 84 | end 85 | end 86 | 87 | def test_randomstring 88 | s1 = OpenID::CryptUtil.random_string(42) 89 | assert_equal(42, s1.length) 90 | s2 = OpenID::CryptUtil.random_string(42) 91 | assert_equal(42, s2.length) 92 | refute_equal(s1, s2) 93 | end 94 | 95 | def test_randomstring_population 96 | s1 = OpenID::CryptUtil.random_string(42, "XO") 97 | assert_match(/[XO]{42}/, s1) 98 | end 99 | 100 | def test_sha1 101 | assert_equal("\x11\xf6\xad\x8e\xc5*)\x84\xab\xaa\xfd|;Qe\x03x\\ r", 102 | OpenID::CryptUtil.sha1('x')) 103 | end 104 | 105 | def test_hmac_sha1 106 | assert_equal("\x8bo\xf7O\xa7\x18*\x90\xac ah\x16\xf7\xb8\x81JB\x9f|", 107 | OpenID::CryptUtil.hmac_sha1('x', 'x')) 108 | end 109 | 110 | def test_sha256 111 | assert_equal("-q\x16B\xb7&\xb0D\x01b|\xa9\xfb\xac2\xf5\xc8S\x0f\xb1\x90<\xc4\xdb\x02%\x87\x17\x92\x1aH\x81", 112 | OpenID::CryptUtil.sha256('x')) 113 | end 114 | 115 | def test_hmac_sha256 116 | assert_equal("\x94{\xd2w\xb2\xd3\\\xfc\x07\xfb\xc7\xe3b\xf2iuXz1\xf8:}\xffx\x8f\xda\xc1\xfaC\xc4\xb2\x87", 117 | OpenID::CryptUtil.hmac_sha256('x', 'x')) 118 | end 119 | end 120 | -------------------------------------------------------------------------------- /test/testutil.rb: -------------------------------------------------------------------------------- 1 | require "pathname" 2 | 3 | if defined? Minitest::Test 4 | # We're on Minitest 5+. Nothing to do here. 5 | else 6 | # Minitest 4 doesn't have Minitest::Test yet. 7 | Minitest::Test = MiniTest::Unit::TestCase 8 | end 9 | 10 | module OpenID 11 | module TestDataMixin 12 | TESTS_DIR = Pathname.new(__FILE__).dirname 13 | TEST_DATA_DIR = Pathname.new('data') 14 | 15 | def read_data_file(filename, lines=true, data_dir=TEST_DATA_DIR) 16 | fname = TESTS_DIR.join(data_dir, filename) 17 | 18 | if lines 19 | fname.readlines 20 | else 21 | fname.read 22 | end 23 | end 24 | end 25 | 26 | module FetcherMixin 27 | def with_fetcher(fetcher) 28 | original_fetcher = OpenID.fetcher 29 | begin 30 | OpenID.fetcher = fetcher 31 | return yield 32 | ensure 33 | OpenID.fetcher = original_fetcher 34 | end 35 | end 36 | end 37 | 38 | module Const 39 | def const(symbol, value) 40 | (class << self;self;end).instance_eval do 41 | define_method(symbol) { value } 42 | end 43 | end 44 | end 45 | 46 | class MockResponse 47 | attr_reader :code, :body 48 | 49 | def initialize(code, body) 50 | @code = code.to_s 51 | @body = body 52 | end 53 | end 54 | 55 | module ProtocolErrorMixin 56 | def assert_protocol_error(str_prefix) 57 | begin 58 | result = yield 59 | rescue ProtocolError => why 60 | message = "Expected prefix #{str_prefix.inspect}, got "\ 61 | "#{why.message.inspect}" 62 | assert(why.message.start_with?(str_prefix), message) 63 | else 64 | fail("Expected ProtocolError. Got #{result.inspect}") 65 | end 66 | end 67 | end 68 | 69 | module OverrideMethodMixin 70 | def with_method_overridden(method_name, proc) 71 | original = method(method_name) 72 | begin 73 | # TODO: find a combination of undef calls which prevent the warning 74 | verbose, $VERBOSE = $VERBOSE, false 75 | define_method(method_name, proc) 76 | module_function(method_name) 77 | $VERBOSE = verbose 78 | yield 79 | ensure 80 | if original.respond_to? :owner 81 | original.owner.send(:undef_method, method_name) 82 | original.owner.send :define_method, method_name, original 83 | else 84 | define_method(method_name, original) 85 | module_function(method_name) 86 | end 87 | end 88 | end 89 | end 90 | 91 | # To use: 92 | # > x = Object.new 93 | # > x.extend(InstanceDefExtension) 94 | # > x.instance_def(:monkeys) do 95 | # > "bananas" 96 | # > end 97 | # > x.monkeys 98 | # 99 | module InstanceDefExtension 100 | def instance_def(method_name, &proc) 101 | (class << self;self;end).instance_eval do 102 | # TODO: find a combination of undef calls which prevent the warning 103 | verbose, $VERBOSE = $VERBOSE, false 104 | define_method(method_name, proc) 105 | $VERBOSE = verbose 106 | end 107 | end 108 | end 109 | 110 | GOODSIG = '[A Good Signature]' 111 | 112 | class GoodAssoc 113 | attr_accessor :handle, :expires_in 114 | 115 | def initialize(handle='-blah-') 116 | @handle = handle 117 | @expires_in = 3600 118 | end 119 | 120 | def check_message_signature(msg) 121 | msg.get_arg(OPENID_NS, 'sig') == GOODSIG 122 | end 123 | end 124 | 125 | class HTTPResponse 126 | def self._from_raw_data(status, body="", headers={}, final_url=nil) 127 | resp = Net::HTTPResponse.new('1.1', status.to_s, 'NONE') 128 | me = self._from_net_response(resp, final_url) 129 | me.initialize_http_header headers 130 | me.body = body 131 | return me 132 | end 133 | end 134 | end 135 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /test/test_trustroot.rb: -------------------------------------------------------------------------------- 1 | require 'minitest/autorun' 2 | require 'testutil' 3 | require 'openid/trustroot' 4 | 5 | class TrustRootTest < Minitest::Test 6 | include OpenID::TestDataMixin 7 | 8 | def _test_sanity(case_, sanity, desc) 9 | tr = OpenID::TrustRoot::TrustRoot.parse(case_) 10 | if sanity == 'sane' 11 | assert !tr.nil? 12 | assert tr.sane?, [case_, desc].join(' ') 13 | assert OpenID::TrustRoot::TrustRoot.check_sanity(case_), [case_, desc].join(' ') 14 | elsif sanity == 'insane' 15 | assert !tr.sane?, [case_, desc].join(' ') 16 | assert !OpenID::TrustRoot::TrustRoot.check_sanity(case_), [case_, desc].join(' ') 17 | else 18 | assert tr.nil?, case_ 19 | end 20 | end 21 | 22 | def _test_match(trust_root, url, expected_match) 23 | tr = OpenID::TrustRoot::TrustRoot.parse(trust_root) 24 | actual_match = tr.validate_url(url) 25 | if expected_match 26 | assert actual_match, [trust_root, url].join(' ') 27 | assert OpenID::TrustRoot::TrustRoot.check_url(trust_root, url) 28 | else 29 | assert !actual_match, [expected_match, actual_match, trust_root, url].join(' ') 30 | assert !OpenID::TrustRoot::TrustRoot.check_url(trust_root, url) 31 | end 32 | end 33 | 34 | def test_trustroots 35 | data = read_data_file('trustroot.txt', false) 36 | 37 | parts = data.split('=' * 40 + "\n").collect { |i| i.strip() } 38 | assert(parts[0] == '') 39 | _, ph, pdat, mh, mdat = parts 40 | 41 | getTests(['bad', 'insane', 'sane'], ph, pdat).each { |tc| 42 | sanity, desc, case_ = tc 43 | _test_sanity(case_, sanity, desc) 44 | } 45 | 46 | getTests([true, false], mh, mdat).each { |tc| 47 | match, _, case_ = tc 48 | trust_root, url = case_.split() 49 | _test_match(trust_root, url, match) 50 | } 51 | end 52 | 53 | def getTests(grps, head, dat) 54 | tests = [] 55 | top = head.strip() 56 | gdat = dat.split('-' * 40 + "\n").collect { |i| i.strip() } 57 | assert gdat[0] == '' 58 | assert gdat.length == (grps.length * 2 + 1) 59 | i = 1 60 | grps.each { |x| 61 | n, desc = gdat[i].split(': ') 62 | cases = gdat[i + 1].split("\n") 63 | assert(cases.length == n.to_i, "Number of cases differs from header count") 64 | cases.each { |case_| 65 | tests += [[x, top + ' - ' + desc, case_]] 66 | } 67 | i += 2 68 | } 69 | 70 | return tests 71 | end 72 | 73 | def test_return_to_matches 74 | data = [ 75 | [[], nil, false], 76 | [[], "", false], 77 | [[], "http://bogus/return_to", false], 78 | [["http://bogus/"], nil, false], 79 | [["://broken/"], nil, false], 80 | [["://broken/"], "http://broken/", false], 81 | [["http://*.broken/"], "http://foo.broken/", false], 82 | [["http://x.broken/"], "http://foo.broken/", false], 83 | [["http://first/", "http://second/path/"], "http://second/?query=x", false], 84 | 85 | [["http://broken/"], "http://broken/", true], 86 | [["http://first/", "http://second/"], "http://second/?query=x", true], 87 | ] 88 | 89 | data.each { |case_| 90 | allowed_return_urls, return_to, expected_result = case_ 91 | actual_result = OpenID::TrustRoot::return_to_matches(allowed_return_urls, 92 | return_to) 93 | assert(expected_result == actual_result) 94 | } 95 | end 96 | 97 | def test_build_discovery_url 98 | data = [ 99 | ["http://foo.com/path", "http://foo.com/path"], 100 | ["http://foo.com/path?foo=bar", "http://foo.com/path?foo=bar"], 101 | ["http://*.bogus.com/path", "http://www.bogus.com/path"], 102 | ["http://*.bogus.com:122/path", "http://www.bogus.com:122/path"], 103 | ] 104 | 105 | data.each { |case_| 106 | trust_root, expected_disco_url = case_ 107 | tr = OpenID::TrustRoot::TrustRoot.parse(trust_root) 108 | actual_disco_url = tr.build_discovery_url() 109 | assert actual_disco_url == expected_disco_url 110 | } 111 | end 112 | end 113 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 |