Commit f70be197 authored by Eric Davis's avatar Eric Davis

Unpacked OpenID gem. #699

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2437 e93f8b46-1217-0410-a6f0-8f06a7374b81
parent 70efee1b
......@@ -54,4 +54,6 @@ Rails::Initializer.run do |config|
# Define your email configuration in email.yml instead.
# It will automatically turn deliveries on
config.action_mailer.perform_deliveries = false
config.gem 'ruby-openid', :lib => 'openid'
end
--- !ruby/object:Gem::Specification
name: ruby-openid
version: !ruby/object:Gem::Version
version: 2.1.4
platform: ruby
authors:
- JanRain, Inc
autorequire: openid
bindir: bin
cert_chain:
date: 2008-12-19 00:00:00 -08:00
default_executable:
dependencies: []
description:
email: openid@janrain.com
executables: []
extensions: []
extra_rdoc_files:
- README
- INSTALL
- LICENSE
- UPGRADE
files:
- examples/README
- examples/active_record_openid_store
- examples/rails_openid
- examples/discover
- examples/active_record_openid_store/lib
- examples/active_record_openid_store/test
- examples/active_record_openid_store/init.rb
- examples/active_record_openid_store/README
- examples/active_record_openid_store/XXX_add_open_id_store_to_db.rb
- examples/active_record_openid_store/XXX_upgrade_open_id_store.rb
- examples/active_record_openid_store/lib/association.rb
- examples/active_record_openid_store/lib/nonce.rb
- examples/active_record_openid_store/lib/open_id_setting.rb
- examples/active_record_openid_store/lib/openid_ar_store.rb
- examples/active_record_openid_store/test/store_test.rb
- examples/rails_openid/app
- examples/rails_openid/components
- examples/rails_openid/config
- examples/rails_openid/db
- examples/rails_openid/doc
- examples/rails_openid/lib
- examples/rails_openid/log
- examples/rails_openid/public
- examples/rails_openid/script
- examples/rails_openid/test
- examples/rails_openid/vendor
- examples/rails_openid/Rakefile
- examples/rails_openid/README
- examples/rails_openid/app/controllers
- examples/rails_openid/app/helpers
- examples/rails_openid/app/models
- examples/rails_openid/app/views
- examples/rails_openid/app/controllers/application.rb
- examples/rails_openid/app/controllers/login_controller.rb
- examples/rails_openid/app/controllers/server_controller.rb
- examples/rails_openid/app/controllers/consumer_controller.rb
- examples/rails_openid/app/helpers/application_helper.rb
- examples/rails_openid/app/helpers/login_helper.rb
- examples/rails_openid/app/helpers/server_helper.rb
- examples/rails_openid/app/views/layouts
- examples/rails_openid/app/views/login
- examples/rails_openid/app/views/server
- examples/rails_openid/app/views/consumer
- examples/rails_openid/app/views/layouts/server.rhtml
- examples/rails_openid/app/views/login/index.rhtml
- examples/rails_openid/app/views/server/decide.rhtml
- examples/rails_openid/app/views/consumer/index.rhtml
- examples/rails_openid/config/environments
- examples/rails_openid/config/database.yml
- examples/rails_openid/config/boot.rb
- examples/rails_openid/config/environment.rb
- examples/rails_openid/config/routes.rb
- examples/rails_openid/config/environments/development.rb
- examples/rails_openid/config/environments/production.rb
- examples/rails_openid/config/environments/test.rb
- examples/rails_openid/doc/README_FOR_APP
- examples/rails_openid/lib/tasks
- examples/rails_openid/public/images
- examples/rails_openid/public/javascripts
- examples/rails_openid/public/stylesheets
- examples/rails_openid/public/dispatch.cgi
- examples/rails_openid/public/404.html
- examples/rails_openid/public/500.html
- examples/rails_openid/public/dispatch.fcgi
- examples/rails_openid/public/dispatch.rb
- examples/rails_openid/public/favicon.ico
- examples/rails_openid/public/robots.txt
- examples/rails_openid/public/images/openid_login_bg.gif
- examples/rails_openid/public/javascripts/controls.js
- examples/rails_openid/public/javascripts/dragdrop.js
- examples/rails_openid/public/javascripts/effects.js
- examples/rails_openid/public/javascripts/prototype.js
- examples/rails_openid/script/performance
- examples/rails_openid/script/process
- examples/rails_openid/script/console
- examples/rails_openid/script/about
- examples/rails_openid/script/breakpointer
- examples/rails_openid/script/destroy
- examples/rails_openid/script/generate
- examples/rails_openid/script/plugin
- examples/rails_openid/script/runner
- examples/rails_openid/script/server
- examples/rails_openid/script/performance/benchmarker
- examples/rails_openid/script/performance/profiler
- examples/rails_openid/script/process/spawner
- examples/rails_openid/script/process/reaper
- examples/rails_openid/script/process/spinner
- examples/rails_openid/test/fixtures
- examples/rails_openid/test/functional
- examples/rails_openid/test/mocks
- examples/rails_openid/test/unit
- examples/rails_openid/test/test_helper.rb
- examples/rails_openid/test/functional/login_controller_test.rb
- examples/rails_openid/test/functional/server_controller_test.rb
- examples/rails_openid/test/mocks/development
- examples/rails_openid/test/mocks/test
- lib/openid
- lib/hmac
- lib/openid.rb
- lib/openid/cryptutil.rb
- lib/openid/extras.rb
- lib/openid/urinorm.rb
- lib/openid/util.rb
- lib/openid/trustroot.rb
- lib/openid/message.rb
- lib/openid/yadis
- lib/openid/consumer
- lib/openid/fetchers.rb
- lib/openid/dh.rb
- lib/openid/kvform.rb
- lib/openid/association.rb
- lib/openid/store
- lib/openid/kvpost.rb
- lib/openid/extensions
- lib/openid/protocolerror.rb
- lib/openid/server.rb
- lib/openid/extension.rb
- lib/openid/consumer.rb
- lib/openid/yadis/htmltokenizer.rb
- lib/openid/yadis/parsehtml.rb
- lib/openid/yadis/filters.rb
- lib/openid/yadis/xrds.rb
- lib/openid/yadis/accept.rb
- lib/openid/yadis/constants.rb
- lib/openid/yadis/discovery.rb
- lib/openid/yadis/xri.rb
- lib/openid/yadis/xrires.rb
- lib/openid/yadis/services.rb
- lib/openid/consumer/html_parse.rb
- lib/openid/consumer/idres.rb
- lib/openid/consumer/associationmanager.rb
- lib/openid/consumer/discovery.rb
- lib/openid/consumer/discovery_manager.rb
- lib/openid/consumer/checkid_request.rb
- lib/openid/consumer/responses.rb
- lib/openid/store/filesystem.rb
- lib/openid/store/interface.rb
- lib/openid/store/nonce.rb
- lib/openid/store/memory.rb
- lib/openid/extensions/sreg.rb
- lib/openid/extensions/ax.rb
- lib/openid/extensions/pape.rb
- lib/hmac/hmac.rb
- lib/hmac/sha1.rb
- lib/hmac/sha2.rb
- test/data
- test/test_association.rb
- test/test_urinorm.rb
- test/testutil.rb
- test/test_util.rb
- test/test_message.rb
- test/test_cryptutil.rb
- test/test_extras.rb
- test/util.rb
- test/test_trustroot.rb
- test/test_parsehtml.rb
- test/test_fetchers.rb
- test/test_dh.rb
- test/test_kvform.rb
- test/test_openid_yadis.rb
- test/test_linkparse.rb
- test/test_stores.rb
- test/test_filters.rb
- test/test_xrds.rb
- test/test_nonce.rb
- test/test_accept.rb
- test/test_kvpost.rb
- test/test_associationmanager.rb
- test/discoverdata.rb
- test/test_server.rb
- test/test_yadis_discovery.rb
- test/test_sreg.rb
- test/test_idres.rb
- test/test_ax.rb
- test/test_xri.rb
- test/test_xrires.rb
- test/test_discover.rb
- test/test_consumer.rb
- test/test_pape.rb
- test/test_checkid_request.rb
- test/test_discovery_manager.rb
- test/test_responses.rb
- test/test_extension.rb
- test/data/test_xrds
- test/data/urinorm.txt
- test/data/n2b64
- test/data/trustroot.txt
- test/data/dh.txt
- test/data/test1-parsehtml.txt
- test/data/linkparse.txt
- test/data/accept.txt
- test/data/test_discover
- test/data/example-xrds.xml
- test/data/test1-discover.txt
- test/data/test_xrds/ref.xrds
- test/data/test_xrds/README
- test/data/test_xrds/delegated-20060809-r1.xrds
- test/data/test_xrds/delegated-20060809-r2.xrds
- test/data/test_xrds/delegated-20060809.xrds
- test/data/test_xrds/no-xrd.xml
- test/data/test_xrds/not-xrds.xml
- test/data/test_xrds/prefixsometimes.xrds
- test/data/test_xrds/sometimesprefix.xrds
- test/data/test_xrds/spoof1.xrds
- test/data/test_xrds/spoof2.xrds
- test/data/test_xrds/spoof3.xrds
- test/data/test_xrds/status222.xrds
- test/data/test_xrds/valid-populated-xrds.xml
- test/data/test_xrds/=j3h.2007.11.14.xrds
- test/data/test_xrds/subsegments.xrds
- test/data/test_discover/openid2_xrds.xml
- test/data/test_discover/openid.html
- test/data/test_discover/openid2.html
- test/data/test_discover/openid2_xrds_no_local_id.xml
- test/data/test_discover/openid_1_and_2.html
- test/data/test_discover/openid_1_and_2_xrds.xml
- test/data/test_discover/openid_and_yadis.html
- test/data/test_discover/openid_1_and_2_xrds_bad_delegate.xml
- test/data/test_discover/openid_no_delegate.html
- test/data/test_discover/yadis_0entries.xml
- test/data/test_discover/yadis_2_bad_local_id.xml
- test/data/test_discover/yadis_2entries_delegate.xml
- test/data/test_discover/yadis_2entries_idp.xml
- test/data/test_discover/yadis_another_delegate.xml
- test/data/test_discover/yadis_idp.xml
- test/data/test_discover/yadis_idp_delegate.xml
- test/data/test_discover/yadis_no_delegate.xml
- test/data/test_discover/malformed_meta_tag.html
- NOTICE
- CHANGELOG
- README
- INSTALL
- LICENSE
- UPGRADE
- admin/runtests.rb
has_rdoc: true
homepage: http://openidenabled.com/ruby-openid/
post_install_message:
rdoc_options:
- --main
- README
require_paths:
- lib
required_ruby_version: !ruby/object:Gem::Requirement
requirements:
- - ">"
- !ruby/object:Gem::Version
version: 0.0.0
version:
required_rubygems_version: !ruby/object:Gem::Requirement
requirements:
- - ">="
- !ruby/object:Gem::Version
version: "0"
version:
requirements: []
rubyforge_project:
rubygems_version: 1.3.1
signing_key:
specification_version: 1
summary: A library for consuming and serving OpenID identities.
test_files:
- admin/runtests.rb
Fri Dec 19 11:50:10 PST 2008 cygnus@janrain.com
tagged 2.1.4
Fri Dec 19 11:48:25 PST 2008 cygnus@janrain.com
* Version: 2.1.4
Fri Dec 19 11:42:47 PST 2008 cygnus@janrain.com
* Normalize XRIs when doing discovery in accordance with the OpenID 2 spec
Tue Dec 16 13:14:07 PST 2008 cygnus@janrain.com
tagged 2.1.3
= Ruby OpenID Library Installation
== Rubygems Installation
Rubygems is a tool for installing ruby libraries and their
dependancies. If you have rubygems installed, simply:
gem install ruby-openid
== Manual Installation
Unpack the archive and run setup.rb to install:
ruby setup.rb
setup.rb installs the library into your system ruby. If don't want to
add openid to you system ruby, you may instead add the *lib* directory of
the extracted tarball to your RUBYLIB environment variable:
$ export RUBYLIB=${RUBYLIB}:/path/to/ruby-openid/lib
== Testing the Installation
Make sure everything installed ok:
$> irb
irb$> require "openid"
=> true
Or, if you installed via rubygems:
$> irb
irb$> require "rubygems"
=> true
irb$> require_gem "ruby-openid"
=> true
== Run the test suite
Go into the test directory and execute the *runtests.rb* script.
== Next steps
* Run consumer.rb in the examples directory.
* Get started writing your own consumer using OpenID::Consumer
* Write your own server with OpenID::Server
* Use the OpenIDLoginGenerator! Read example/README for more info.
This diff is collapsed.
This product includes software developed by JanRain,
available from http://openidenabled.com/
=Ruby OpenID
A Ruby library for verifying and serving OpenID identities.
==Features
* Easy to use API for verifying OpenID identites - OpenID::Consumer
* Support for serving OpenID identites - OpenID::Server
* Does not depend on underlying web framework
* Supports multiple storage mechanisms (Filesystem, ActiveRecord, Memory)
* Example code to help you get started, including:
* Ruby on Rails based consumer and server
* OpenIDLoginGenerator for quickly getting creating a rails app that uses
OpenID for authentication
* ActiveRecordOpenIDStore plugin
* Comprehensive test suite
* Supports both OpenID 1 and OpenID 2 transparently
==Installing
Before running the examples or writing your own code you'll need to install
the library. See the INSTALL file or use rubygems:
gem install ruby-openid
Check the installation:
$ irb
irb> require 'rubygems'
irb> require_gem 'ruby-openid'
=> true
The library is known to work with Ruby 1.8.4 on Unix, Max OSX and
Win32. Examples have been tested with Rails 1.1 and 1.2, and 2.0.
==Getting Started
The best way to start is to look at the rails_openid example.
You can run it with:
cd examples/rails_openid
script/server
If you are writing an OpenID Relying Party, a good place to start is:
examples/rails_openid/app/controllers/consumer_controller.rb
And if you are writing an OpenID provider:
examples/rails_openid/app/controllers/server_controller.rb
The library code is quite well documented, so don't be squeamish, and
look at the library itself if there's anything you don't understand in
the examples.
==Homepage
http://openidenabled.com/ruby-openid/
See also:
http://openid.net/
http://openidenabled.com/
==Community
Discussion regarding the Ruby OpenID library and other JanRain OpenID
libraries takes place on the the OpenID mailing list on
openidenabled.com.
http://lists.openidenabled.com/mailman/listinfo/dev
Please join this list to discuss, ask implementation questions, report
bugs, etc. Also check out the openid channel on the freenode IRC
network.
If you have a bugfix or feature you'd like to contribute, don't
hesitate to send it to us. For more detailed information on how to
contribute, see
http://openidenabled.com/contribute/
==Author
Copyright 2006-2008, JanRain, Inc.
Contact openid@janrain.com or visit the OpenID channel on pibb.com:
http://pibb.com/go/openid
==License
Apache Software License. For more information see the LICENSE file.
= Upgrading from the OpenID 1.x series library
== Consumer Upgrade
The flow is largely the same, however there are a number of significant
changes. The consumer example is helpful to look at:
examples/rails_openid/app/controllers/consumer_controller.rb
=== Stores
You will need to require the file for the store that you are using.
For the filesystem store, this is 'openid/stores/filesystem'
They are also now in modules. The filesystem store is
OpenID::Store::Filesystem
The format has changed, and you should remove your old store directory.
The ActiveRecord store ( examples/active_record_openid_store ) still needs
to be put in a plugin directory for your rails app. There's a migration
that needs to be run; examine the README in that directory.
Also, note that the stores now can be garbage collected with the method
store.cleanup
=== Starting the OpenID transaction
The OpenIDRequest object no longer has status codes. Instead,
consumer.begin raises an OpenID::OpenIDError if there is a problem
initiating the transaction, so you'll want something along the lines of:
begin
openid_request = consumer.begin(params[:openid_identifier])
rescue OpenID::OpenIDError => e
# display error e
return
end
#success case
Data regarding the OpenID server once lived in
openid_request.service
The corresponding object in the 2.0 lib can be retrieved with
openid_request.endpoint
Getting the unverified identifier: Where you once had
openid_request.identity_url
you will now want
openid_request.endpoint.claimed_id
which might be different from what you get at the end of the transaction,
since it is now possible for users to enter their server's url directly.
Arguments on the return_to URL are now verified, so if you want to add
additional arguments to the return_to url, use
openid_request.return_to_args['param'] = value
Generating the redirect is the same as before, but add any extensions
first.
If you need to set up an SSL certificate authority list for the fetcher,
use the 'ca_file' attr_accessor on the OpenID::StandardFetcher. This has
changed from 'ca_path' in the 1.x.x series library. That is, set
OpenID.fetcher.ca_file = '/path/to/ca.list'
before calling consumer.begin.
=== Requesting Simple Registration Data
You'll need to require the code for the extension
require 'openid/extensions/sreg'
The new code for adding an SReg request now looks like:
sreg_request = OpenID::SReg::Request.new
sreg_request.request_fields(['email', 'dob'], true) # required
sreg_request.request_fields(['nickname', 'fullname'], false) # optional
sreg_request.policy_url = policy_url
openid_request.add_extension(sreg_request)
The code for adding other extensions is similar. Code for the Attribute
Exchange (AX) and Provider Authentication Policy Extension (PAPE) are
included with the library, and additional extensions can be implemented
subclassing OpenID::Extension.
=== Completing the transaction
The return_to and its arguments are verified, so you need to pass in
the base URL and the arguments. With Rails, the params method mashes
together parameters from GET, POST, and the path, so you'll need to pull
off the path "parameters" with something like
return_to = url_for(:only_path => false,
:controller => 'openid',
:action => 'complete')
parameters = params.reject{|k,v| request.path_parameters[k] }
openid_response = consumer.complete(parameters, return_to)
The response still uses the status codes, but they are now namespaced
slightly differently, for example OpenID::Consumer::SUCCESS
In the case of failure, the error message is now found in
openid_response.message
The identifier to display to the user can be found in
openid_response.endpoint.display_identifier
The Simple Registration response can be read from the OpenID response
with
sreg_response = OpenID::SReg::Response.from_success_response(openid_response)
nickname = sreg_response['nickname']
# etc.
== Server Upgrade
The server code is mostly the same as before, with the exception of
extensions. Also, you must pass in the endpoint URL to the server
constructor:
@server = OpenID::Server.new(store, server_url)
I recommend looking at
examples/rails_openid/app/controllers/server_controller.rb
for an example of the new way of doing extensions.
--
Dag Arneson, JanRain Inc.
Please direct questions to openid@janrain.com
#!/usr/bin/ruby
require "logger"
require "stringio"
require "pathname"
require 'test/unit/collector/dir'
require 'test/unit/ui/console/testrunner'
def main
old_verbose = $VERBOSE
$VERBOSE = true
tests_dir = Pathname.new(__FILE__).dirname.dirname.join('test')
# Collect tests from everything named test_*.rb.
c = Test::Unit::Collector::Dir.new
if c.respond_to?(:base=)
# In order to supress warnings from ruby 1.8.6 about accessing
# undefined member
c.base = tests_dir
suite = c.collect
else
# Because base is not defined in ruby < 1.8.6
suite = c.collect(tests_dir)
end
result = Test::Unit::UI::Console::TestRunner.run(suite)
result.passed?
ensure
$VERBOSE = old_verbose
end
exit(main)
This directory contains several examples that demonstrate use of the
OpenID library. Make sure you have properly installed the library
before running the examples. These examples are a great place to
start in integrating OpenID into your application.
==Rails example
The rails_openid contains a fully functional OpenID server and relying
party, and acts as a starting point for implementing your own
production rails server. You'll need the latest version of Ruby on
Rails installed, and then:
cd rails_openid
./script/server
Open a web browser to http://localhost:3000/ and follow the instructions.
The relevant code to work from when writing your Rails OpenID Relying
Party is:
rails_openid/app/controllers/consumer_controller.rb
If you are working on an OpenID provider, check out
rails_openid/app/controllers/server_controller.rb
Since the library and examples are Apache-licensed, don't be shy about
copy-and-paste.
==Rails ActiveRecord OpenIDStore plugin
For various reasons you may want or need to deploy your ruby openid
consumer/server using an SQL based store. The active_record_openid_store
is a plugin that makes using an SQL based store simple. Follow the
README inside the plugin's dir for usage.
=Active Record OpenID Store Plugin
A store is required by an OpenID server and optionally by the consumer
to store associations, nonces, and auth key information across
requests and processes. If rails is distributed across several
machines, they must must all have access to the same OpenID store
data, so the FilesystemStore won't do.
This directory contains a plugin for connecting your
OpenID enabled rails app to an ActiveRecord based OpenID store.
==Install
1) Copy this directory and all it's contents into your
RAILS_ROOT/vendor/plugins directory. You structure should look like
this:
RAILS_ROOT/vendor/plugins/active_record_openid_store/
2) Copy the migration, XXX_add_open_id_store_to_db.rb to your
RAILS_ROOT/db/migrate directory. Rename the XXX portion of the
file to next sequential migration number.
3) Run the migration:
rake migrate
4) Change your app to use the ActiveRecordOpenIDStore:
store = ActiveRecordOpenIDStore.new
consumer = OpenID::Consumer.new(session, store)
5) That's it! All your OpenID state will now be stored in the database.
==Upgrade
If you are upgrading from the 1.x ActiveRecord store, replace your old
RAILS_ROOT/vendor/plugins/active_record_openid_store/ directory with
the new one and run the migration XXX_upgrade_open_id_store.rb.
==What about garbage collection?
You may garbage collect unused nonces and expired associations using
the gc instance method of ActiveRecordOpenIDStore. Hook it up to a
task in your app's Rakefile like so:
desc 'GC OpenID store'
task :gc_openid_store => :environment do
ActiveRecordOpenIDStore.new.cleanup
end
Run it by typing:
rake gc_openid_store
==Questions?
Contact Dag Arneson: dag at janrain dot com
# Use this migration to create the tables for the ActiveRecord store
class AddOpenIdStoreToDb < ActiveRecord::Migration
def self.up
create_table "open_id_associations", :force => true do |t|
t.column "server_url", :binary, :null => false
t.column "handle", :string, :null => false
t.column "secret", :binary, :null => false
t.column "issued", :integer, :null => false
t.column "lifetime", :integer, :null => false
t.column "assoc_type", :string, :null => false
end
create_table "open_id_nonces", :force => true do |t|
t.column :server_url, :string, :null => false
t.column :timestamp, :integer, :null => false
t.column :salt, :string, :null => false
end
end
def self.down
drop_table "open_id_associations"
drop_table "open_id_nonces"
end
end
# Use this migration to upgrade the old 1.1 ActiveRecord store schema
# to the new 2.0 schema.
class UpgradeOpenIdStore < ActiveRecord::Migration
def self.up
drop_table "open_id_settings"
drop_table "open_id_nonces"
create_table "open_id_nonces", :force => true do |t|
t.column :server_url, :string, :null => false
t.column :timestamp, :integer, :null => false
t.column :salt, :string, :null => false
end
end
def self.down
drop_table "open_id_nonces"
create_table "open_id_nonces", :force => true do |t|
t.column "nonce", :string
t.column "created", :integer
end
create_table "open_id_settings", :force => true do |t|
t.column "setting", :string
t.column "value", :binary
end
end
end
# might using the ruby-openid gem
begin
require 'rubygems'
rescue LoadError
nil
end
require 'openid'
require 'openid_ar_store'
require 'openid/association'
require 'time'
class Association < ActiveRecord::Base
set_table_name 'open_id_associations'
def from_record
OpenID::Association.new(handle, secret, Time.at(issued), lifetime, assoc_type)
end
end
class Nonce < ActiveRecord::Base
set_table_name 'open_id_nonces'
end
class OpenIdSetting < ActiveRecord::Base
validates_uniqueness_of :setting
end
require 'association'
require 'nonce'
require 'openid/store/interface'
# not in OpenID module to avoid namespace conflict
class ActiveRecordStore < OpenID::Store::Interface
def store_association(server_url, assoc)
remove_association(server_url, assoc.handle)
Association.create!(:server_url => server_url,
:handle => assoc.handle,
:secret => assoc.secret,
:issued => assoc.issued.to_i,
:lifetime => assoc.lifetime,
:assoc_type => assoc.assoc_type)
end
def get_association(server_url, handle=nil)
assocs = if handle.blank?
Association.find_all_by_server_url(server_url)
else
Association.find_all_by_server_url_and_handle(server_url, handle)
end
assocs.reverse.each do |assoc|
a = assoc.from_record
if a.expires_in == 0
assoc.destroy
else
return a
end
end if assocs.any?
return nil
end
def remove_association(server_url, handle)
Association.delete_all(['server_url = ? AND handle = ?', server_url, handle]) > 0
end
def use_nonce(server_url, timestamp, salt)
return false if Nonce.find_by_server_url_and_timestamp_and_salt(server_url, timestamp, salt)
return false if (timestamp - Time.now.to_i).abs > OpenID::Nonce.skew
Nonce.create!(:server_url => server_url, :timestamp => timestamp, :salt => salt)
return true
end
def cleanup_nonces
now = Time.now.to_i
Nonce.delete_all(["timestamp > ? OR timestamp < ?", now + OpenID::Nonce.skew, now - OpenID::Nonce.skew])
end
def cleanup_associations
now = Time.now.to_i
Association.delete_all(['issued + lifetime > ?',now])
end
end
$:.unshift(File.dirname(__FILE__) + '/../lib')
require 'test/unit'
RAILS_ENV = "test"
require File.expand_path(File.join(File.dirname(__FILE__), '../../../../config/environment.rb'))
module StoreTestCase
@@allowed_handle = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~'
@@allowed_nonce = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
def _gen_nonce
OpenID::CryptUtil.random_string(8, @@allowed_nonce)
end
def _gen_handle(n)
OpenID::CryptUtil.random_string(n, @@allowed_handle)
end
def _gen_secret(n, chars=nil)
OpenID::CryptUtil.random_string(n, chars)
end
def _gen_assoc(issued, lifetime=600)
secret = _gen_secret(20)
handle = _gen_handle(128)
OpenID::Association.new(handle, secret, Time.now + issued, lifetime,
'HMAC-SHA1')
end
def _check_retrieve(url, handle=nil, expected=nil)
ret_assoc = @store.get_association(url, handle)
if expected.nil?
assert_nil(ret_assoc)
else
assert_equal(expected, ret_assoc)
assert_equal(expected.handle, ret_assoc.handle)
assert_equal(expected.secret, ret_assoc.secret)
end
end
def _check_remove(url, handle, expected)
present = @store.remove_association(url, handle)
assert_equal(expected, present)
end
def test_store
server_url = "http://www.myopenid.com/openid"
assoc = _gen_assoc(issued=0)
# Make sure that a missing association returns no result
_check_retrieve(server_url)
# Check that after storage, getting returns the same result
@store.store_association(server_url, assoc)
_check_retrieve(server_url, nil, assoc)
# more than once
_check_retrieve(server_url, nil, assoc)
# Storing more than once has no ill effect
@store.store_association(server_url, assoc)
_check_retrieve(server_url, nil, assoc)
# Removing an association that does not exist returns not present
_check_remove(server_url, assoc.handle + 'x', false)
# Removing an association that does not exist returns not present
_check_remove(server_url + 'x', assoc.handle, false)
# Removing an association that is present returns present
_check_remove(server_url, assoc.handle, true)
# but not present on subsequent calls
_check_remove(server_url, assoc.handle, false)
# Put assoc back in the store
@store.store_association(server_url, assoc)
# More recent and expires after assoc
assoc2 = _gen_assoc(issued=1)
@store.store_association(server_url, assoc2)
# After storing an association with a different handle, but the
# same server_url, the handle with the later expiration is returned.
_check_retrieve(server_url, nil, assoc2)
# We can still retrieve the older association
_check_retrieve(server_url, assoc.handle, assoc)
# Plus we can retrieve the association with the later expiration
# explicitly
_check_retrieve(server_url, assoc2.handle, assoc2)
# More recent, and expires earlier than assoc2 or assoc. Make sure
# that we're picking the one with the latest issued date and not
# taking into account the expiration.
assoc3 = _gen_assoc(issued=2, lifetime=100)
@store.store_association(server_url, assoc3)
_check_retrieve(server_url, nil, assoc3)
_check_retrieve(server_url, assoc.handle, assoc)
_check_retrieve(server_url, assoc2.handle, assoc2)
_check_retrieve(server_url, assoc3.handle, assoc3)
_check_remove(server_url, assoc2.handle, true)
_check_retrieve(server_url, nil, assoc3)
_check_retrieve(server_url, assoc.handle, assoc)
_check_retrieve(server_url, assoc2.handle, nil)
_check_retrieve(server_url, assoc3.handle, assoc3)
_check_remove(server_url, assoc2.handle, false)
_check_remove(server_url, assoc3.handle, true)
_check_retrieve(server_url, nil, assoc)
_check_retrieve(server_url, assoc.handle, assoc)
_check_retrieve(server_url, assoc2.handle, nil)
_check_retrieve(server_url, assoc3.handle, nil)
_check_remove(server_url, assoc2.handle, false)
_check_remove(server_url, assoc.handle, true)
_check_remove(server_url, assoc3.handle, false)
_check_retrieve(server_url, nil, nil)
_check_retrieve(server_url, assoc.handle, nil)
_check_retrieve(server_url, assoc2.handle, nil)
_check_retrieve(server_url, assoc3.handle, nil)
_check_remove(server_url, assoc2.handle, false)
_check_remove(server_url, assoc.handle, false)
_check_remove(server_url, assoc3.handle, false)
assocValid1 = _gen_assoc(-3600, 7200)
assocValid2 = _gen_assoc(-5)
assocExpired1 = _gen_assoc(-7200, 3600)
assocExpired2 = _gen_assoc(-7200, 3600)
@store.cleanup_associations
@store.store_association(server_url + '1', assocValid1)
@store.store_association(server_url + '1', assocExpired1)
@store.store_association(server_url + '2', assocExpired2)
@store.store_association(server_url + '3', assocValid2)
cleaned = @store.cleanup_associations()
assert_equal(2, cleaned, "cleaned up associations")
end
def _check_use_nonce(nonce, expected, server_url, msg='')
stamp, salt = OpenID::Nonce::split_nonce(nonce)
actual = @store.use_nonce(server_url, stamp, salt)
assert_equal(expected, actual, msg)
end
def test_nonce
server_url = "http://www.myopenid.com/openid"
[server_url, ''].each{|url|
nonce1 = OpenID::Nonce::mk_nonce
_check_use_nonce(nonce1, true, url, "#{url}: nonce allowed by default")
_check_use_nonce(nonce1, false, url, "#{url}: nonce not allowed twice")
_check_use_nonce(nonce1, false, url, "#{url}: nonce not allowed third time")
# old nonces shouldn't pass
old_nonce = OpenID::Nonce::mk_nonce(3600)
_check_use_nonce(old_nonce, false, url, "Old nonce #{old_nonce.inspect} passed")
}
now = Time.now.to_i
old_nonce1 = OpenID::Nonce::mk_nonce(now - 20000)
old_nonce2 = OpenID::Nonce::mk_nonce(now - 10000)
recent_nonce = OpenID::Nonce::mk_nonce(now - 600)
orig_skew = OpenID::Nonce.skew
OpenID::Nonce.skew = 0
count = @store.cleanup_nonces
OpenID::Nonce.skew = 1000000
ts, salt = OpenID::Nonce::split_nonce(old_nonce1)
assert(@store.use_nonce(server_url, ts, salt), "oldnonce1")
ts, salt = OpenID::Nonce::split_nonce(old_nonce2)
assert(@store.use_nonce(server_url, ts, salt), "oldnonce2")
ts, salt = OpenID::Nonce::split_nonce(recent_nonce)
assert(@store.use_nonce(server_url, ts, salt), "recent_nonce")
OpenID::Nonce.skew = 1000
cleaned = @store.cleanup_nonces
assert_equal(2, cleaned, "Cleaned #{cleaned} nonces")
OpenID::Nonce.skew = 100000
ts, salt = OpenID::Nonce::split_nonce(old_nonce1)
assert(@store.use_nonce(server_url, ts, salt), "oldnonce1 after cleanup")
ts, salt = OpenID::Nonce::split_nonce(old_nonce2)
assert(@store.use_nonce(server_url, ts, salt), "oldnonce2 after cleanup")
ts, salt = OpenID::Nonce::split_nonce(recent_nonce)
assert(!@store.use_nonce(server_url, ts, salt), "recent_nonce after cleanup")
OpenID::Nonce.skew = orig_skew
end
end
class TestARStore < Test::Unit::TestCase
include StoreTestCase
def setup
@store = ActiveRecordStore.new
end
end
#!/usr/bin/env ruby
require "openid/consumer/discovery"
require 'openid/fetchers'
OpenID::fetcher_use_env_http_proxy
$names = [[:server_url, "Server URL "],
[:local_id, "Local ID "],
[:canonical_id, "Canonical ID"],
]
def show_services(user_input, normalized, services)
puts " Claimed identifier: #{normalized}"
if services.empty?
puts " No OpenID services found"
puts
else
puts " Discovered services:"
n = 0
services.each do |service|
n += 1
puts " #{n}."
$names.each do |meth, name|
val = service.send(meth)
if val
printf(" %s: %s\n", name, val)
end
end
puts " Type URIs:"
for type_uri in service.type_uris
puts " * #{type_uri}"
end
puts
end
end
end
ARGV.each do |openid_identifier|
puts "=" * 50
puts "Running discovery on #{openid_identifier}"
begin
normalized_identifier, services = OpenID.discover(openid_identifier)
rescue OpenID::DiscoveryFailure => why
puts "Discovery failed: #{why.message}"
puts
else
show_services(openid_identifier, normalized_identifier, services)
end
end
== Welcome to Rails
Rails is a web-application and persistence framework that includes everything
needed to create database-backed web-applications according to the
Model-View-Control pattern of separation. This pattern splits the view (also
called the presentation) into "dumb" templates that are primarily responsible
for inserting pre-built data in between HTML tags. The model contains the
"smart" domain objects (such as Account, Product, Person, Post) that holds all
the business logic and knows how to persist themselves to a database. The
controller handles the incoming requests (such as Save New Account, Update
Product, Show Post) by manipulating the model and directing data to the view.
In Rails, the model is handled by what's called an object-relational mapping
layer entitled Active Record. This layer allows you to present the data from
database rows as objects and embellish these data objects with business logic
methods. You can read more about Active Record in
link:files/vendor/rails/activerecord/README.html.
The controller and view are handled by the Action Pack, which handles both
layers by its two parts: Action View and Action Controller. These two layers
are bundled in a single package due to their heavy interdependence. This is
unlike the relationship between the Active Record and Action Pack that is much
more separate. Each of these packages can be used independently outside of
Rails. You can read more about Action Pack in
link:files/vendor/rails/actionpack/README.html.
== Getting started
1. Run the WEBrick servlet: <tt>ruby script/server</tt> (run with --help for options)
...or if you have lighttpd installed: <tt>ruby script/lighttpd</tt> (it's faster)
2. Go to http://localhost:3000/ and get "Congratulations, you've put Ruby on Rails!"
3. Follow the guidelines on the "Congratulations, you've put Ruby on Rails!" screen
== Example for Apache conf
<VirtualHost *:80>
ServerName rails
DocumentRoot /path/application/public/
ErrorLog /path/application/log/server.log
<Directory /path/application/public/>
Options ExecCGI FollowSymLinks
AllowOverride all
Allow from all
Order allow,deny
</Directory>
</VirtualHost>
NOTE: Be sure that CGIs can be executed in that directory as well. So ExecCGI
should be on and ".cgi" should respond. All requests from 127.0.0.1 go
through CGI, so no Apache restart is necessary for changes. All other requests
go through FCGI (or mod_ruby), which requires a restart to show changes.
== Debugging Rails
Have "tail -f" commands running on both the server.log, production.log, and
test.log files. Rails will automatically display debugging and runtime
information to these files. Debugging info will also be shown in the browser
on requests from 127.0.0.1.
== Breakpoints
Breakpoint support is available through the script/breakpointer client. This
means that you can break out of execution at any point in the code, investigate
and change the model, AND then resume execution! Example:
class WeblogController < ActionController::Base
def index
@posts = Post.find_all
breakpoint "Breaking out from the list"
end
end
So the controller will accept the action, run the first line, then present you
with a IRB prompt in the breakpointer window. Here you can do things like:
Executing breakpoint "Breaking out from the list" at .../webrick_server.rb:16 in 'breakpoint'
>> @posts.inspect
=> "[#<Post:0x14a6be8 @attributes={\"title\"=>nil, \"body\"=>nil, \"id\"=>\"1\"}>,
#<Post:0x14a6620 @attributes={\"title\"=>\"Rails you know!\", \"body\"=>\"Only ten..\", \"id\"=>\"2\"}>]"
>> @posts.first.title = "hello from a breakpoint"
=> "hello from a breakpoint"
...and even better is that you can examine how your runtime objects actually work:
>> f = @posts.first
=> #<Post:0x13630c4 @attributes={"title"=>nil, "body"=>nil, "id"=>"1"}>
>> f.
Display all 152 possibilities? (y or n)
Finally, when you're ready to resume execution, you press CTRL-D
== Console
You can interact with the domain model by starting the console through script/console.
Here you'll have all parts of the application configured, just like it is when the
application is running. You can inspect domain models, change values, and save to the
database. Starting the script without arguments will launch it in the development environment.
Passing an argument will specify a different environment, like <tt>console production</tt>.
== Description of contents
app
Holds all the code that's specific to this particular application.
app/controllers
Holds controllers that should be named like weblog_controller.rb for
automated URL mapping. All controllers should descend from
ActionController::Base.
app/models
Holds models that should be named like post.rb.
Most models will descend from ActiveRecord::Base.
app/views
Holds the template files for the view that should be named like
weblog/index.rhtml for the WeblogController#index action. All views use eRuby
syntax. This directory can also be used to keep stylesheets, images, and so on
that can be symlinked to public.
app/helpers
Holds view helpers that should be named like weblog_helper.rb.
config
Configuration files for the Rails environment, the routing map, the database, and other dependencies.
components
Self-contained mini-applications that can bundle together controllers, models, and views.
lib
Application specific libraries. Basically, any kind of custom code that doesn't
belong under controllers, models, or helpers. This directory is in the load path.
public
The directory available for the web server. Contains subdirectories for images, stylesheets,
and javascripts. Also contains the dispatchers and the default HTML files.
script
Helper scripts for automation and generation.
test
Unit and functional tests along with fixtures.
vendor
External libraries that the application depends on. Also includes the plugins subdirectory.
This directory is in the load path.
# Add your own tasks in files placed in lib/tasks ending in .rake,
# for example lib/tasks/switchtower.rake, and they will automatically be available to Rake.
require(File.join(File.dirname(__FILE__), 'config', 'boot'))
require 'rake'
require 'rake/testtask'
require 'rake/rdoctask'
require 'tasks/rails'
\ No newline at end of file
# Filters added to this controller will be run for all controllers in the application.
# Likewise, all the methods added will be available for all controllers.
class ApplicationController < ActionController::Base
end
\ No newline at end of file
require 'pathname'
require "openid"
require 'openid/extensions/sreg'
require 'openid/extensions/pape'
require 'openid/store/filesystem'
class ConsumerController < ApplicationController
layout nil
def index
# render an openid form
end
def start
begin
identifier = params[:openid_identifier]
if identifier.nil?
flash[:error] = "Enter an OpenID identifier"
redirect_to :action => 'index'
return
end
oidreq = consumer.begin(identifier)
rescue OpenID::OpenIDError => e
flash[:error] = "Discovery failed for #{identifier}: #{e}"
redirect_to :action => 'index'
return
end
if params[:use_sreg]
sregreq = OpenID::SReg::Request.new
# required fields
sregreq.request_fields(['email','nickname'], true)
# optional fields
sregreq.request_fields(['dob', 'fullname'], false)
oidreq.add_extension(sregreq)
oidreq.return_to_args['did_sreg'] = 'y'
end
if params[:use_pape]
papereq = OpenID::PAPE::Request.new
papereq.add_policy_uri(OpenID::PAPE::AUTH_PHISHING_RESISTANT)
papereq.max_auth_age = 2*60*60
oidreq.add_extension(papereq)
oidreq.return_to_args['did_pape'] = 'y'
end
if params[:force_post]
oidreq.return_to_args['force_post']='x'*2048
end
return_to = url_for :action => 'complete', :only_path => false
realm = url_for :action => 'index', :only_path => false
if oidreq.send_redirect?(realm, return_to, params[:immediate])
redirect_to oidreq.redirect_url(realm, return_to, params[:immediate])
else
render :text => oidreq.html_markup(realm, return_to, params[:immediate], {'id' => 'openid_form'})
end
end
def complete
# FIXME - url_for some action is not necessarily the current URL.
current_url = url_for(:action => 'complete', :only_path => false)
parameters = params.reject{|k,v|request.path_parameters[k]}
oidresp = consumer.complete(parameters, current_url)
case oidresp.status
when OpenID::Consumer::FAILURE
if oidresp.display_identifier
flash[:error] = ("Verification of #{oidresp.display_identifier}"\
" failed: #{oidresp.message}")
else
flash[:error] = "Verification failed: #{oidresp.message}"
end
when OpenID::Consumer::SUCCESS
flash[:success] = ("Verification of #{oidresp.display_identifier}"\
" succeeded.")
if params[:did_sreg]
sreg_resp = OpenID::SReg::Response.from_success_response(oidresp)
sreg_message = "Simple Registration data was requested"
if sreg_resp.empty?
sreg_message << ", but none was returned."
else
sreg_message << ". The following data were sent:"
sreg_resp.data.each {|k,v|
sreg_message << "<br/><b>#{k}</b>: #{v}"
}
end
flash[:sreg_results] = sreg_message
end
if params[:did_pape]
pape_resp = OpenID::PAPE::Response.from_success_response(oidresp)
pape_message = "A phishing resistant authentication method was requested"
if pape_resp.auth_policies.member? OpenID::PAPE::AUTH_PHISHING_RESISTANT
pape_message << ", and the server reported one."
else
pape_message << ", but the server did not report one."
end
if pape_resp.auth_time
pape_message << "<br><b>Authentication time:</b> #{pape_resp.auth_time} seconds"
end
if pape_resp.nist_auth_level
pape_message << "<br><b>NIST Auth Level:</b> #{pape_resp.nist_auth_level}"
end
flash[:pape_results] = pape_message
end
when OpenID::Consumer::SETUP_NEEDED
flash[:alert] = "Immediate request failed - Setup Needed"
when OpenID::Consumer::CANCEL
flash[:alert] = "OpenID transaction cancelled."
else
end
redirect_to :action => 'index'
end
private
def consumer
if @consumer.nil?
dir = Pathname.new(RAILS_ROOT).join('db').join('cstore')
store = OpenID::Store::Filesystem.new(dir)
@consumer = OpenID::Consumer.new(session, store)
end
return @consumer
end
end
# Controller for handling the login, logout process for "users" of our
# little server. Users have no password. This is just an example.
require 'openid'
class LoginController < ApplicationController
layout 'server'
def base_url
url_for(:controller => 'login', :action => nil, :only_path => false)
end
def index
response.headers['X-XRDS-Location'] = url_for(:controller => "server",
:action => "idp_xrds",
:only_path => false)
@base_url = base_url
# just show the login page
end
def submit
user = params[:username]
# if we get a user, log them in by putting their username in
# the session hash.
unless user.nil?
session[:username] = user unless user.nil?
session[:approvals] = []
flash[:notice] = "Your OpenID URL is <b>#{base_url}user/#{user}</b><br/><br/>Proceed to step 2 below."
else
flash[:error] = "Sorry, couldn't log you in. Try again."
end
redirect_to :action => 'index'
end
def logout
# delete the username from the session hash
session[:username] = nil
session[:approvals] = nil
redirect_to :action => 'index'
end
end
require 'pathname'
# load the openid library, first trying rubygems
#begin
# require "rubygems"
# require_gem "ruby-openid", ">= 1.0"
#rescue LoadError
require "openid"
require "openid/consumer/discovery"
require 'openid/extensions/sreg'
require 'openid/extensions/pape'
require 'openid/store/filesystem'
#end
class ServerController < ApplicationController
include ServerHelper
include OpenID::Server
layout nil
def index
begin
oidreq = server.decode_request(params)
rescue ProtocolError => e
# invalid openid request, so just display a page with an error message
render :text => e.to_s, :status => 500
return
end
# no openid.mode was given
unless oidreq
render :text => "This is an OpenID server endpoint."
return
end
oidresp = nil
if oidreq.kind_of?(CheckIDRequest)
identity = oidreq.identity
if oidreq.id_select
if oidreq.immediate
oidresp = oidreq.answer(false)
elsif session[:username].nil?
# The user hasn't logged in.
show_decision_page(oidreq)
return
else
# Else, set the identity to the one the user is using.
identity = url_for_user
end
end
if oidresp
nil
elsif self.is_authorized(identity, oidreq.trust_root)
oidresp = oidreq.answer(true, nil, identity)
# add the sreg response if requested
add_sreg(oidreq, oidresp)
# ditto pape
add_pape(oidreq, oidresp)
elsif oidreq.immediate
server_url = url_for :action => 'index'
oidresp = oidreq.answer(false, server_url)
else
show_decision_page(oidreq)
return
end
else
oidresp = server.handle_request(oidreq)
end
self.render_response(oidresp)
end
def show_decision_page(oidreq, message="Do you trust this site with your identity?")
session[:last_oidreq] = oidreq
@oidreq = oidreq
if message
flash[:notice] = message
end
render :template => 'server/decide', :layout => 'server'
end
def user_page
# Yadis content-negotiation: we want to return the xrds if asked for.
accept = request.env['HTTP_ACCEPT']
# This is not technically correct, and should eventually be updated
# to do real Accept header parsing and logic. Though I expect it will work
# 99% of the time.
if accept and accept.include?('application/xrds+xml')
user_xrds
return
end
# content negotiation failed, so just render the user page
xrds_url = url_for(:controller=>'user',:action=>params[:username])+'/xrds'
identity_page = <<EOS
<html><head>
<meta http-equiv="X-XRDS-Location" content="#{xrds_url}" />
<link rel="openid.server" href="#{url_for :action => 'index'}" />
</head><body><p>OpenID identity page for #{params[:username]}</p>
</body></html>
EOS
# Also add the Yadis location header, so that they don't have
# to parse the html unless absolutely necessary.
response.headers['X-XRDS-Location'] = xrds_url
render :text => identity_page
end
def user_xrds
types = [
OpenID::OPENID_2_0_TYPE,
OpenID::OPENID_1_0_TYPE,
OpenID::SREG_URI,
]
render_xrds(types)
end
def idp_xrds
types = [
OpenID::OPENID_IDP_2_0_TYPE,
]
render_xrds(types)
end
def decision
oidreq = session[:last_oidreq]
session[:last_oidreq] = nil
if params[:yes].nil?
redirect_to oidreq.cancel_url
return
else
id_to_send = params[:id_to_send]
identity = oidreq.identity
if oidreq.id_select
if id_to_send and id_to_send != ""
session[:username] = id_to_send
session[:approvals] = []
identity = url_for_user
else
msg = "You must enter a username to in order to send " +
"an identifier to the Relying Party."
show_decision_page(oidreq, msg)
return
end
end
if session[:approvals]
session[:approvals] << oidreq.trust_root
else
session[:approvals] = [oidreq.trust_root]
end
oidresp = oidreq.answer(true, nil, identity)
add_sreg(oidreq, oidresp)
add_pape(oidreq, oidresp)
return self.render_response(oidresp)
end
end
protected
def server
if @server.nil?
server_url = url_for :action => 'index', :only_path => false
dir = Pathname.new(RAILS_ROOT).join('db').join('openid-store')
store = OpenID::Store::Filesystem.new(dir)
@server = Server.new(store, server_url)
end
return @server
end
def approved(trust_root)
return false if session[:approvals].nil?
return session[:approvals].member?(trust_root)
end
def is_authorized(identity_url, trust_root)
return (session[:username] and (identity_url == url_for_user) and self.approved(trust_root))
end
def render_xrds(types)
type_str = ""
types.each { |uri|
type_str += "<Type>#{uri}</Type>\n "
}
yadis = <<EOS
<?xml version="1.0" encoding="UTF-8"?>
<xrds:XRDS
xmlns:xrds="xri://$xrds"
xmlns="xri://$xrd*($v*2.0)">
<XRD>
<Service priority="0">
#{type_str}
<URI>#{url_for(:controller => 'server', :only_path => false)}</URI>
</Service>
</XRD>
</xrds:XRDS>
EOS
response.headers['content-type'] = 'application/xrds+xml'
render :text => yadis
end
def add_sreg(oidreq, oidresp)
# check for Simple Registration arguments and respond
sregreq = OpenID::SReg::Request.from_openid_request(oidreq)
return if sregreq.nil?
# In a real application, this data would be user-specific,
# and the user should be asked for permission to release
# it.
sreg_data = {
'nickname' => session[:username],
'fullname' => 'Mayor McCheese',
'email' => 'mayor@example.com'
}
sregresp = OpenID::SReg::Response.extract_response(sregreq, sreg_data)
oidresp.add_extension(sregresp)
end
def add_pape(oidreq, oidresp)
papereq = OpenID::PAPE::Request.from_openid_request(oidreq)
return if papereq.nil?
paperesp = OpenID::PAPE::Response.new
paperesp.nist_auth_level = 0 # we don't even do auth at all!
oidresp.add_extension(paperesp)
end
def render_response(oidresp)
if oidresp.needs_signing
signed_response = server.signatory.sign(oidresp)
end
web_response = server.encode_response(oidresp)
case web_response.code
when HTTP_OK
render :text => web_response.body, :status => 200
when HTTP_REDIRECT
redirect_to web_response.headers['location']
else
render :text => web_response.body, :status => 400
end
end
end
# Methods added to this helper will be available to all templates in the application.
module ApplicationHelper
end
module ServerHelper
def url_for_user
url_for :controller => 'user', :action => session[:username]
end
end
<html>
<head>
<title>Rails OpenID Example Relying Party</title>
</head>
<style type="text/css">
* {
font-family: verdana,sans-serif;
}
body {
width: 50em;
margin: 1em;
}
div {
padding: .5em;
}
.alert {
border: 1px solid #e7dc2b;
background: #fff888;
}
.error {
border: 1px solid #ff0000;
background: #ffaaaa;
}
.success {
border: 1px solid #00ff00;
background: #aaffaa;
}
#verify-form {
border: 1px solid #777777;
background: #dddddd;
margin-top: 1em;
padding-bottom: 0em;
}
input.openid {
background: url( /images/openid_login_bg.gif ) no-repeat;
background-position: 0 50%;
background-color: #fff;
padding-left: 18px;
}
</style>
<body>
<h1>Rails OpenID Example Relying Party</h1>
<% if flash[:alert] %>
<div class='alert'>
<%= h(flash[:alert]) %>
</div>
<% end %>
<% if flash[:error] %>
<div class='error'>
<%= h(flash[:error]) %>
</div>
<% end %>
<% if flash[:success] %>
<div class='success'>
<%= h(flash[:success]) %>
</div>
<% end %>
<% if flash[:sreg_results] %>
<div class='alert'>
<%= flash[:sreg_results] %>
</div>
<% end %>
<% if flash[:pape_results] %>
<div class='alert'>
<%= flash[:pape_results] %>
</div>
<% end %>
<div id="verify-form">
<form method="get" accept-charset="UTF-8"
action='<%= url_for :action => 'start' %>'>
Identifier:
<input type="text" class="openid" name="openid_identifier" />
<input type="submit" value="Verify" /><br />
<input type="checkbox" name="immediate" id="immediate" /><label for="immediate">Use immediate mode</label><br/>
<input type="checkbox" name="use_sreg" id="use_sreg" /><label for="use_sreg">Request registration data</label><br/>
<input type="checkbox" name="use_pape" id="use_pape" /><label for="use_pape">Request phishing-resistent auth policy (PAPE)</label><br/>
<input type="checkbox" name="force_post" id="force_post" /><label for="force_post">Force the transaction to use POST by adding 2K of extra data</label>
</form>
</div>
</body>
</html>
<html>
<head><title>OpenID Server Example</title></head>
<style type="text/css">
* {
font-family: verdana,sans-serif;
}
body {
width: 50em;
margin: 1em;
}
div {
padding: .5em;
}
table {
margin: none;
padding: none;
}
.notice {
border: 1px solid #60964f;
background: #b3dca7;
}
.error {
border: 1px solid #ff0000;
background: #ffaaaa;
}
#login-form {
border: 1px solid #777777;
background: #dddddd;
margin-top: 1em;
padding-bottom: 0em;
}
table {
padding: 1em;
}
li {margin-bottom: .5em;}
span.openid:before {
content: url(<%= @base_url %>images/openid_login_bg.gif) ;
}
span.openid {
font-size: smaller;
}
</style>
<body>
<% if session[:username] %>
<div style="float:right;">
Welcome, <%= session[:username] %> | <%= link_to('Log out', :controller => 'login', :action => 'logout') %><br />
<span class="openid"><%= @base_url %>user/<%= session[:username] %></span>
</div>
<% end %>
<h3>Ruby OpenID Server Example</h3>
<hr/>
<% if flash[:notice] or flash[:error] %>
<div class="<%= flash[:notice].nil? ? 'error' : 'notice' %>">
<%= flash[:error] or flash[:notice] %>
</div>
<% end %>
<%= @content_for_layout %>
</body>
</html>
<% if session[:username].nil? %>
<div id="login-form">
<form method="get" action="<%= url_for :controller => 'login', :action => 'submit' %>">
Type a username:
<input type="text" name="username" />
<input type="submit" value="Log In" />
</form>
</div>
<% end %>
<p> Welcome to the Ruby OpenID example. This code is a starting point
for developers wishing to implement an OpenID provider or relying
party. We've used the <a href="http://rubyonrails.org/">Rails</a>
platform to demonstrate, but the library code is not Rails specific.</p>
<h2>To use the example provider</h2>
<p>
<ol>
<li>Enter a username in the form above. You will be "Logged In"
to the server, at which point you may authenticate using an OpenID
consumer. Your OpenID URL will be displayed after you log
in.<p>The server will automatically create an identity page for
you at <%= @base_url %>user/<i>name</i></p></li>
<li><p>Because WEBrick can only handle one thing at a time, you'll need to
run another instance of the example on another port if you want to use
a relying party to use with this example provider:</p>
<blockquote>
<code>script/server --port=3001</code>
</blockquote>
<p>(The RP needs to be able to access the provider, so unless you're
running this example on a public IP, you can't use the live example
at <a href="http://openidenabled.com/">openidenabled.com</a> on
your local provider.)</p>
</li>
<li>Point your browser to this new instance and follow the directions
below.</li>
<!-- Fun fact: 'url_for :port => 3001' doesn't work very well. -->
</ol>
</p>
<h2>To use the example relying party</h2>
<p>Visit <a href="<%= url_for :controller => 'consumer' %>">/consumer</a>
and enter your OpenID.</p>
</p>
<form method="post" action="<%= url_for :controller => 'server', :action => 'decision' %>">
<table>
<tr><td>Site:</td><td><%= @oidreq.trust_root %></td></tr>
<% if @oidreq.id_select %>
<tr>
<td colspan="2">
You entered the server identifier at the relying party.
You'll need to send an identifier of your choosing. Enter a
username below.
</td>
</tr>
<tr>
<td>Identity to send:</td>
<td><input type="text" name="id_to_send" size="25" /></td>
</tr>
<% else %>
<tr><td>Identity:</td><td><%= @oidreq.identity %></td></tr>
<% end %>
</table>
<input type="submit" name="yes" value="yes" />
<input type="submit" name="no" value="no" />
</form>
# Don't change this file. Configuration is done in config/environment.rb and config/environments/*.rb
unless defined?(RAILS_ROOT)
root_path = File.join(File.dirname(__FILE__), '..')
unless RUBY_PLATFORM =~ /mswin32/
require 'pathname'
root_path = Pathname.new(root_path).cleanpath(true).to_s
end
RAILS_ROOT = root_path
end
if File.directory?("#{RAILS_ROOT}/vendor/rails")
require "#{RAILS_ROOT}/vendor/rails/railties/lib/initializer"
else
require 'rubygems'
require 'initializer'
end
Rails::Initializer.run(:set_load_path)
# Be sure to restart your web server when you modify this file.
# Uncomment below to force Rails into production mode when
# you don't control web/app server and can't set it the proper way
# ENV['RAILS_ENV'] ||= 'production'
# Bootstrap the Rails environment, frameworks, and default configuration
require File.join(File.dirname(__FILE__), 'boot')
Rails::Initializer.run do |config|
# Settings in config/environments/* take precedence those specified here
# Skip frameworks you're not going to use
# config.frameworks -= [ :action_web_service, :action_mailer ]
# Add additional load paths for your own custom dirs
# config.load_paths += %W( #{RAILS_ROOT}/extras )
# Force all environments to use the same logger level
# (by default production uses :info, the others :debug)
# config.log_level = :debug
# Use the database for sessions instead of the file system
# (create the session table with 'rake create_sessions_table')
# config.action_controller.session_store = :active_record_store
# Enable page/fragment caching by setting a file-based store
# (remember to create the caching directory and make it readable to the application)
# config.action_controller.fragment_cache_store = :file_store, "#{RAILS_ROOT}/cache"
# Activate observers that should always be running
# config.active_record.observers = :cacher, :garbage_collector
# Make Active Record use UTC-base instead of local time
# config.active_record.default_timezone = :utc
# Use Active Record's schema dumper instead of SQL when creating the test database
# (enables use of different database adapters for development and test environments)
# config.active_record.schema_format = :ruby
# See Rails::Configuration for more options
end
# Add new inflection rules using the following format
# (all these examples are active by default):
# Inflector.inflections do |inflect|
# inflect.plural /^(ox)$/i, '\1en'
# inflect.singular /^(ox)en/i, '\1'
# inflect.irregular 'person', 'people'
# inflect.uncountable %w( fish sheep )
# end
# Include your application configuration below
ActionController::CgiRequest::DEFAULT_SESSION_OPTIONS[:session_key] = '_session_id_2'
# Settings specified here will take precedence over those in config/environment.rb
# In the development environment your application's code is reloaded on
# every request. This slows down response time but is perfect for development
# since you don't have to restart the webserver when you make code changes.
config.cache_classes = false
# Log error messages when you accidentally call methods on nil.
config.whiny_nils = true
# Enable the breakpoint server that script/breakpointer connects to
config.breakpoint_server = true
# Show full error reports and disable caching
config.action_controller.consider_all_requests_local = true
config.action_controller.perform_caching = false
# Don't care if the mailer can't send
config.action_mailer.raise_delivery_errors = false
# Settings specified here will take precedence over those in config/environment.rb
# The production environment is meant for finished, "live" apps.
# Code is not reloaded between requests
config.cache_classes = true
# Use a different logger for distributed setups
# config.logger = SyslogLogger.new
# Full error reports are disabled and caching is turned on
config.action_controller.consider_all_requests_local = false
config.action_controller.perform_caching = true
# Enable serving of images, stylesheets, and javascripts from an asset server
# config.action_controller.asset_host = "http://assets.example.com"
# Disable delivery errors if you bad email addresses should just be ignored
# config.action_mailer.raise_delivery_errors = false
# Settings specified here will take precedence over those in config/environment.rb
# The test environment is used exclusively to run your application's
# test suite. You never need to work with it otherwise. Remember that
# your test database is "scratch space" for the test suite and is wiped
# and recreated between test runs. Don't rely on the data there!
config.cache_classes = true
# Log error messages when you accidentally call methods on nil.
config.whiny_nils = true
# Show full error reports and disable caching
config.action_controller.consider_all_requests_local = true
config.action_controller.perform_caching = false
# Tell ActionMailer not to deliver emails to the real world.
# The :test delivery method accumulates sent emails in the
# ActionMailer::Base.deliveries array.
config.action_mailer.delivery_method = :test
\ No newline at end of file
ActionController::Routing::Routes.draw do |map|
# Add your own custom routes here.
# The priority is based upon order of creation: first created -> highest priority.
# Here's a sample route:
# map.connect 'products/:id', :controller => 'catalog', :action => 'view'
# Keep in mind you can assign values other than :controller and :action
# You can have the root of your site routed by hooking up ''
# -- just remember to delete public/index.html.
# map.connect '', :controller => "welcome"
map.connect '', :controller => 'login'
map.connect 'server/xrds', :controller => 'server', :action => 'idp_xrds'
map.connect 'user/:username', :controller => 'server', :action => 'user_page'
map.connect 'user/:username/xrds', :controller => 'server', :action => 'user_xrds'
# Allow downloading Web Service WSDL as a file with an extension
# instead of a file named 'wsdl'
map.connect ':controller/service.wsdl', :action => 'wsdl'
# Install the default route as the lowest priority.
map.connect ':controller/:action/:id'
end
Use this README file to introduce your application and point to useful places in the API for learning more.
Run "rake appdoc" to generate API documentation for your models and controllers.
\ No newline at end of file
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<html>
<body>
<h1>File not found</h1>
<p>Change this error message for pages not found in public/404.html</p>
</body>
</html>
\ No newline at end of file
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<html>
<body>
<h1>Application error (Apache)</h1>
<p>Change this error message for exceptions thrown outside of an action (like in Dispatcher setups or broken Ruby code) in public/500.html</p>
</body>
</html>
\ No newline at end of file
#!/usr/bin/ruby1.8
#!/usr/local/bin/ruby
require File.dirname(__FILE__) + "/../config/environment" unless defined?(RAILS_ROOT)
# If you're using RubyGems and mod_ruby, this require should be changed to an absolute path one, like:
# "/usr/local/lib/ruby/gems/1.8/gems/rails-0.8.0/lib/dispatcher" -- otherwise performance is severely impaired
require "dispatcher"
ADDITIONAL_LOAD_PATHS.reverse.each { |dir| $:.unshift(dir) if File.directory?(dir) } if defined?(Apache::RubyRun)
Dispatcher.dispatch
\ No newline at end of file
#!/usr/bin/ruby1.8
#!/usr/local/bin/ruby
#
# You may specify the path to the FastCGI crash log (a log of unhandled
# exceptions which forced the FastCGI instance to exit, great for debugging)
# and the number of requests to process before running garbage collection.
#
# By default, the FastCGI crash log is RAILS_ROOT/log/fastcgi.crash.log
# and the GC period is nil (turned off). A reasonable number of requests
# could range from 10-100 depending on the memory footprint of your app.
#
# Example:
# # Default log path, normal GC behavior.
# RailsFCGIHandler.process!
#
# # Default log path, 50 requests between GC.
# RailsFCGIHandler.process! nil, 50
#
# # Custom log path, normal GC behavior.
# RailsFCGIHandler.process! '/var/log/myapp_fcgi_crash.log'
#
require File.dirname(__FILE__) + "/../config/environment"
require 'fcgi_handler'
RailsFCGIHandler.process!
#!/usr/bin/ruby1.8
#!/usr/local/bin/ruby
require File.dirname(__FILE__) + "/../config/environment" unless defined?(RAILS_ROOT)
# If you're using RubyGems and mod_ruby, this require should be changed to an absolute path one, like:
# "/usr/local/lib/ruby/gems/1.8/gems/rails-0.8.0/lib/dispatcher" -- otherwise performance is severely impaired
require "dispatcher"
ADDITIONAL_LOAD_PATHS.reverse.each { |dir| $:.unshift(dir) if File.directory?(dir) } if defined?(Apache::RubyRun)
Dispatcher.dispatch
\ No newline at end of file
# See http://www.robotstxt.org/wc/norobots.html for documentation on how to use the robots.txt file
\ No newline at end of file
#!/usr/bin/env ruby
require File.dirname(__FILE__) + '/../config/boot'
require 'commands/about'
\ No newline at end of file
#!/usr/bin/env ruby
require File.dirname(__FILE__) + '/../config/boot'
require 'commands/breakpointer'
\ No newline at end of file
#!/usr/bin/env ruby
require File.dirname(__FILE__) + '/../config/boot'
require 'commands/console'
\ No newline at end of file
#!/usr/bin/env ruby
require File.dirname(__FILE__) + '/../config/boot'
require 'commands/destroy'
\ No newline at end of file
#!/usr/bin/env ruby
require File.dirname(__FILE__) + '/../config/boot'
require 'commands/generate'
\ No newline at end of file
#!/usr/bin/env ruby
require File.dirname(__FILE__) + '/../../config/boot'
require 'commands/performance/benchmarker'
#!/usr/bin/env ruby
require File.dirname(__FILE__) + '/../../config/boot'
require 'commands/performance/profiler'
#!/usr/bin/env ruby
require File.dirname(__FILE__) + '/../config/boot'
require 'commands/plugin'
\ No newline at end of file
#!/usr/bin/env ruby
require File.dirname(__FILE__) + '/../../config/boot'
require 'commands/process/reaper'
#!/usr/bin/env ruby
require File.dirname(__FILE__) + '/../../config/boot'
require 'commands/process/spawner'
#!/usr/bin/env ruby
require File.dirname(__FILE__) + '/../../config/boot'
require 'commands/process/spinner'
#!/usr/bin/env ruby
require File.dirname(__FILE__) + '/../config/boot'
require 'commands/runner'
\ No newline at end of file
#!/usr/bin/env ruby
require File.dirname(__FILE__) + '/../config/boot'
require 'commands/server'
\ No newline at end of file
require File.dirname(__FILE__) + '/../test_helper'
require 'login_controller'
# Re-raise errors caught by the controller.
class LoginController; def rescue_action(e) raise e end; end
class LoginControllerTest < Test::Unit::TestCase
def setup
@controller = LoginController.new
@request = ActionController::TestRequest.new
@response = ActionController::TestResponse.new
end
# Replace this with your real tests.
def test_truth
assert true
end
end
require File.dirname(__FILE__) + '/../test_helper'
require 'server_controller'
# Re-raise errors caught by the controller.
class ServerController; def rescue_action(e) raise e end; end
class ServerControllerTest < Test::Unit::TestCase
def setup
@controller = ServerController.new
@request = ActionController::TestRequest.new
@response = ActionController::TestResponse.new
end
# Replace this with your real tests.
def test_truth
assert true
end
end
ENV["RAILS_ENV"] = "test"
require File.expand_path(File.dirname(__FILE__) + "/../config/environment")
require 'test_help'
class Test::Unit::TestCase
# Transactional fixtures accelerate your tests by wrapping each test method
# in a transaction that's rolled back on completion. This ensures that the
# test database remains unchanged so your fixtures don't have to be reloaded
# between every test method. Fewer database queries means faster tests.
#
# Read Mike Clark's excellent walkthrough at
# http://clarkware.com/cgi/blosxom/2005/10/24#Rails10FastTesting
#
# Every Active Record database supports transactions except MyISAM tables
# in MySQL. Turn off transactional fixtures in this case; however, if you
# don't care one way or the other, switching from MyISAM to InnoDB tables
# is recommended.
self.use_transactional_fixtures = true
# Instantiated fixtures are slow, but give you @david where otherwise you
# would need people(:david). If you don't want to migrate your existing
# test cases which use the @david style and don't mind the speed hit (each
# instantiated fixtures translates to a database query per test method),
# then set this back to true.
self.use_instantiated_fixtures = false
# Add more helper methods to be used by all tests here...
end
# Copyright (C) 2001 Daiki Ueno <ueno@unixuser.org>
# This library is distributed under the terms of the Ruby license.
# This module provides common interface to HMAC engines.
# HMAC standard is documented in RFC 2104:
#
# H. Krawczyk et al., "HMAC: Keyed-Hashing for Message Authentication",
# RFC 2104, February 1997
#
# These APIs are inspired by JCE 1.2's javax.crypto.Mac interface.
#
# <URL:http://java.sun.com/security/JCE1.2/spec/apidoc/javax/crypto/Mac.html>
module HMAC
class Base
def initialize(algorithm, block_size, output_length, key)
@algorithm = algorithm
@block_size = block_size
@output_length = output_length
@status = STATUS_UNDEFINED
@key_xor_ipad = ''
@key_xor_opad = ''
set_key(key) unless key.nil?
end
private
def check_status
unless @status == STATUS_INITIALIZED
raise RuntimeError,
"The underlying hash algorithm has not yet been initialized."
end
end
public
def set_key(key)
# If key is longer than the block size, apply hash function
# to key and use the result as a real key.
key = @algorithm.digest(key) if key.size > @block_size
key_xor_ipad = "\x36" * @block_size
key_xor_opad = "\x5C" * @block_size
for i in 0 .. key.size - 1
key_xor_ipad[i] ^= key[i]
key_xor_opad[i] ^= key[i]
end
@key_xor_ipad = key_xor_ipad
@key_xor_opad = key_xor_opad
@md = @algorithm.new
@status = STATUS_INITIALIZED
end
def reset_key
@key_xor_ipad.gsub!(/./, '?')
@key_xor_opad.gsub!(/./, '?')
@key_xor_ipad[0..-1] = ''
@key_xor_opad[0..-1] = ''
@status = STATUS_UNDEFINED
end
def update(text)
check_status
# perform inner H
md = @algorithm.new
md.update(@key_xor_ipad)
md.update(text)
str = md.digest
# perform outer H
md = @algorithm.new
md.update(@key_xor_opad)
md.update(str)
@md = md
end
alias << update
def digest
check_status
@md.digest
end
def hexdigest
check_status
@md.hexdigest
end
alias to_s hexdigest
# These two class methods below are safer than using above
# instance methods combinatorially because an instance will have
# held a key even if it's no longer in use.
def Base.digest(key, text)
begin
hmac = self.new(key)
hmac.update(text)
hmac.digest
ensure
hmac.reset_key
end
end
def Base.hexdigest(key, text)
begin
hmac = self.new(key)
hmac.update(text)
hmac.hexdigest
ensure
hmac.reset_key
end
end
private_class_method :new, :digest, :hexdigest
end
STATUS_UNDEFINED, STATUS_INITIALIZED = 0, 1
end
require 'hmac/hmac'
require 'digest/sha1'
module HMAC
class SHA1 < Base
def initialize(key = nil)
super(Digest::SHA1, 64, 20, key)
end
public_class_method :new, :digest, :hexdigest
end
end
require 'hmac/hmac'
require 'digest/sha2'
module HMAC
class SHA256 < Base
def initialize(key = nil)
super(Digest::SHA256, 64, 32, key)
end
public_class_method :new, :digest, :hexdigest
end
class SHA384 < Base
def initialize(key = nil)
super(Digest::SHA384, 128, 48, key)
end
public_class_method :new, :digest, :hexdigest
end
class SHA512 < Base
def initialize(key = nil)
super(Digest::SHA512, 128, 64, key)
end
public_class_method :new, :digest, :hexdigest
end
end
# Copyright 2006-2007 JanRain, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you
# may not use this file except in compliance with the License. You may
# obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied. See the License for the specific language governing
# permissions and limitations under the License.
module OpenID
VERSION = "2.1.4"
end
require "openid/consumer"
require 'openid/server'
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
module OpenID
class Consumer
# A set of discovered services, for tracking which providers have
# been attempted for an OpenID identifier
class DiscoveredServices
attr_reader :current
def initialize(starting_url, yadis_url, services)
@starting_url = starting_url
@yadis_url = yadis_url
@services = services.dup
@current = nil
end
def next
@current = @services.shift
end
def for_url?(url)
[@starting_url, @yadis_url].member?(url)
end
def started?
!@current.nil?
end
def empty?
@services.empty?
end
end
# Manages calling discovery and tracking which endpoints have
# already been attempted.
class DiscoveryManager
def initialize(session, url, session_key_suffix=nil)
@url = url
@session = session
@session_key_suffix = session_key_suffix || 'auth'
end
def get_next_service
manager = get_manager
if !manager.nil? && manager.empty?
destroy_manager
manager = nil
end
if manager.nil?
yadis_url, services = yield @url
manager = create_manager(yadis_url, services)
end
if !manager.nil?
service = manager.next
store(manager)
else
service = nil
end
return service
end
def cleanup(force=false)
manager = get_manager(force)
if !manager.nil?
service = manager.current
destroy_manager(force)
else
service = nil
end
return service
end
protected
def get_manager(force=false)
manager = load
if force || manager.nil? || manager.for_url?(@url)
return manager
else
return nil
end
end
def create_manager(yadis_url, services)
manager = get_manager
if !manager.nil?
raise StandardError, "There is already a manager for #{yadis_url}"
end
if services.empty?
return nil
end
manager = DiscoveredServices.new(@url, yadis_url, services)
store(manager)
return manager
end
def destroy_manager(force=false)
if !get_manager(force).nil?
destroy!
end
end
def session_key
'OpenID::Consumer::DiscoveredServices::' + @session_key_suffix
end
def store(manager)
@session[session_key] = manager
end
def load
@session[session_key]
end
def destroy!
@session[session_key] = nil
end
end
end
end
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
class String
def starts_with?(other)
head = self[0, other.length]
head == other
end
def ends_with?(other)
tail = self[-1 * other.length, other.length]
tail == other
end
end
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
require 'openid/util'
module OpenID
# An error in the OpenID protocol
class ProtocolError < OpenIDError
end
end
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment