Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Release v1.0

  • Loading branch information...
commit 83f05ca01b03e5000f0772e119c7a5a6e5d305c0 1 parent 83cb915
@binarylogic binarylogic authored
Showing with 1,402 additions and 23 deletions.
  1. +3 −0  CHANGELOG.rdoc
  2. +0 −2  README.rdoc
  3. +2 −2 Rakefile
  4. +1 −2  init.rb
  5. +25 −7 lib/authlogic_openid/acts_as_authentic.rb
  6. +20 −2 lib/authlogic_openid/session.rb
  7. +3 −8 lib/authlogic_openid/version.rb
  8. +90 −0 test/acts_as_authentic_test.rb
  9. +9 −0 test/fixtures/users.yml
  10. +35 −0 test/libs/open_id_authentication/CHANGELOG
  11. +231 −0 test/libs/open_id_authentication/README
  12. +22 −0 test/libs/open_id_authentication/Rakefile
  13. +11 −0 .../open_id_authentication/generators/open_id_authentication_tables/open_id_authentication_tables_generator.rb
  14. +20 −0 test/libs/open_id_authentication/generators/open_id_authentication_tables/templates/migration.rb
  15. +26 −0 test/libs/open_id_authentication/generators/upgrade_open_id_authentication_tables/templates/migration.rb
  16. +11 −0 ...ication/generators/upgrade_open_id_authentication_tables/upgrade_open_id_authentication_tables_generator.rb
  17. +18 −0 test/libs/open_id_authentication/init.rb
  18. +244 −0 test/libs/open_id_authentication/lib/open_id_authentication.rb
  19. +9 −0 test/libs/open_id_authentication/lib/open_id_authentication/association.rb
  20. +55 −0 test/libs/open_id_authentication/lib/open_id_authentication/db_store.rb
  21. +73 −0 test/libs/open_id_authentication/lib/open_id_authentication/mem_cache_store.rb
  22. +5 −0 test/libs/open_id_authentication/lib/open_id_authentication/nonce.rb
  23. +23 −0 test/libs/open_id_authentication/lib/open_id_authentication/request.rb
  24. +20 −0 test/libs/open_id_authentication/lib/open_id_authentication/timeout_fixes.rb
  25. +30 −0 test/libs/open_id_authentication/tasks/open_id_authentication_tasks.rake
  26. +151 −0 test/libs/open_id_authentication/test/mem_cache_store_test.rb
  27. +32 −0 test/libs/open_id_authentication/test/normalize_test.rb
  28. +46 −0 test/libs/open_id_authentication/test/open_id_authentication_test.rb
  29. +14 −0 test/libs/open_id_authentication/test/status_test.rb
  30. +17 −0 test/libs/open_id_authentication/test/test_helper.rb
  31. +41 −0 test/libs/rails_trickery.rb
  32. +3 −0  test/libs/user.rb
  33. +2 −0  test/libs/user_session.rb
  34. +32 −0 test/session_test.rb
  35. +78 −0 test/test_helper.rb
View
3  CHANGELOG.rdoc
@@ -0,0 +1,3 @@
+== 1.0.0
+
+* Initial release
View
2  README.rdoc
@@ -2,8 +2,6 @@
Authlogic OpenID is an extension of the Authlogic library to add OpenID support. Authlogic v2.0 introduced an enhanced API that makes "plugging in" alternate authentication methods as easy as installing a gem.
-**DISCLAIMER**: This library is in it's beta stage. It is working properly in my sample application, and I have yet to encounter a problem with it, but I still need to write some tests and document the code. The only reason I released this in its current state is because some people have been waiting to use it.
-
== Install and use
=== 1. Make some simple changes to your database:
View
4 Rakefile
@@ -4,7 +4,7 @@ require "rubygems"
require "hoe"
require File.dirname(__FILE__) << "/lib/authlogic_openid/version"
-Hoe.new("Authlogic OpenID", AuthlogicOpenId::Version::STRING) do |p|
+Hoe.new("Authlogic OpenID", AuthlogicOpenid::Version::STRING) do |p|
p.name = "authlogic-oid"
p.author = "Ben Johnson of Binary Logic"
p.email = 'bjohnson@binarylogic.com'
@@ -14,6 +14,6 @@ Hoe.new("Authlogic OpenID", AuthlogicOpenId::Version::STRING) do |p|
p.history_file = "CHANGELOG.rdoc"
p.readme_file = "README.rdoc"
p.extra_rdoc_files = ["CHANGELOG.rdoc", "README.rdoc"]
- p.test_globs = ["test/*/test_*.rb", "test/*/*_test.rb"]
+ p.test_globs = ["test/*/test_*.rb", "test/*_test.rb", "test/*/*_test.rb"]
p.extra_deps = %w(authlogic)
end
View
3  init.rb
@@ -1,2 +1 @@
-require "authlogic"
-require "authlogic_openid"
+require "authlogic_openid"
View
32 lib/authlogic_openid/acts_as_authentic.rb
@@ -1,5 +1,11 @@
+# This module is responsible for adding OpenID functionality to Authlogic. Checkout the README for more info and please
+# see the sub modules for detailed documentation.
module AuthlogicOpenid
+ # This module is responsible for adding in the OpenID functionality to your models. It hooks itself into the
+ # acts_as_authentic method provided by Authlogic.
module ActsAsAuthentic
+ # Adds in the neccesary modules for acts_as_authentic to include and also disabled password validation if
+ # OpenID is being used.
def self.included(klass)
klass.class_eval do
add_acts_as_authentic_module(Methods)
@@ -10,6 +16,7 @@ def self.included(klass)
end
module Methods
+ # Set up some simple validations
def self.included(klass)
klass.class_eval do
validates_uniqueness_of :openid_identifier, :scope => validations_scope, :if => :using_openid?
@@ -17,6 +24,7 @@ def self.included(klass)
end
end
+ # Set the openid_identifier field and also resets the persistence_token if this value changes.
def openid_identifier=(value)
write_attribute(:openid_identifier, value.blank? ? nil : OpenIdAuthentication.normalize_identifier(value))
reset_persistence_token if openid_identifier_changed?
@@ -24,8 +32,18 @@ def openid_identifier=(value)
@openid_error = e.message
end
- def save(*args, &block)
- if !authenticate_with_openid? || (authenticate_with_openid? && authenticate_with_openid)
+ # This is where all of the magic happens. This is where we hook in and add all of the OpenID sweetness.
+ #
+ # I had to take this approach because when authenticating with OpenID nonces and what not are stored in database
+ # tables. That being said, the whole save process for ActiveRecord is wrapped in a transaction. Trying to authenticate
+ # with OpenID in a transaction is not good because that transaction be get rolled back, thus reversing all of the OpenID
+ # inserts and making OpenID authentication fail every time. So We need to step outside of the transaction and do our OpenID
+ # madness.
+ #
+ # Another advantage of taking this approach is that we can set fields from their OpenID profile before we save the record,
+ # if their OpenID provider supports it.
+ def save(perform_validation = true, &block)
+ if !perform_validation || !authenticate_with_openid? || (authenticate_with_openid? && authenticate_with_openid)
result = super
yield(result) if block_given?
result
@@ -60,7 +78,7 @@ def authenticate_with_openid
if result.unsuccessful?
@openid_error = result.message
else
- map_openid_fields(registration)
+ map_openid_registration(registration)
end
return true
@@ -69,10 +87,10 @@ def authenticate_with_openid
return false
end
- def map_openid_fields(registration)
- self.name = registration[:fullname] if respond_to?(:name) && !registration[:fullname].blank?
- self.first_name = registration[:fullname].split(" ").first if respond_to?(:first_name) && !registration[:fullname].blank?
- self.first_name = registration[:fullname].split(" ").last if respond_to?(:last_name) && !registration[:last_name].blank?
+ def map_openid_registration(registration)
+ self.name ||= registration[:fullname] if respond_to?(:name) && !registration[:fullname].blank?
+ self.first_name ||= registration[:fullname].split(" ").first if respond_to?(:first_name) && !registration[:fullname].blank?
+ self.first_name ||= registration[:fullname].split(" ").last if respond_to?(:last_name) && !registration[:last_name].blank?
end
def validate_openid
View
22 lib/authlogic_openid/session.rb
@@ -1,19 +1,33 @@
module AuthlogicOpenid
+ # This module is responsible for adding all of the OpenID goodness to the Authlogic::Session::Base class.
module Session
+ # Add a simple openid_identifier attribute and some validations for the field.
def self.included(klass)
klass.class_eval do
- attr_accessor :openid_identifier
+ attr_reader :openid_identifier
+ validate :validate_openid_error
validate :validate_by_openid, :if => :authenticating_with_openid?
end
end
-
+
+ # Hooks into credentials so that you can pass an :openid_identifier key.
def credentials=(value)
super
values = value.is_a?(Array) ? value : [value]
hash = values.first.is_a?(Hash) ? values.first.with_indifferent_access : nil
self.openid_identifier = hash[:openid_identifier] if !hash.nil? && hash.key?(:openid_identifier)
end
+
+ def openid_identifier=(value)
+ @openid_identifier = value.blank? ? nil : OpenIdAuthentication.normalize_identifier(value)
+ @openid_error = nil
+ rescue OpenIdAuthentication::InvalidOpenId => e
+ @openid_identifier = nil
+ @openid_error = e.message
+ end
+ # Cleaers out the block if we are authenticating with OpenID, so that we can redirect without a DoubleRender
+ # error.
def save(&block)
block = nil if !openid_identifier.blank?
super(&block)
@@ -39,5 +53,9 @@ def validate_by_openid
end
end
end
+
+ def validate_openid_error
+ errors.add(:openid_identifier, @openid_error) if @openid_error
+ end
end
end
View
11 lib/authlogic_openid/version.rb
@@ -1,11 +1,8 @@
-module AuthlogicOpenid # :nodoc:
- # = Version
- #
+module AuthlogicOpenid
# A class for describing the current version of a library. The version
# consists of three parts: the +major+ number, the +minor+ number, and the
# +tiny+ (or +patch+) number.
class Version
-
include Comparable
# A convenience method for instantiating a new Version instance with the
@@ -42,15 +39,13 @@ def to_a
[@major, @minor, @tiny]
end
- MAJOR = 0
+ MAJOR = 1
MINOR = 0
- TINY = 9
+ TINY = 0
# The current version as a Version instance
CURRENT = new(MAJOR, MINOR, TINY)
# The current version as a String
STRING = CURRENT.to_s
-
end
-
end
View
90 test/acts_as_authentic_test.rb
@@ -0,0 +1,90 @@
+require File.dirname(__FILE__) + '/test_helper.rb'
+
+class ActsAsAuthenticTest < ActiveSupport::TestCase
+ def test_included
+ assert User.send(:acts_as_authentic_modules).include?(AuthlogicOpenid::ActsAsAuthentic::Methods)
+ assert_equal :validate_password_with_openid?, User.validates_length_of_password_field_options[:if]
+ assert_equal :validate_password_with_openid?, User.validates_confirmation_of_password_field_options[:if]
+ assert_equal :validate_password_with_openid?, User.validates_length_of_password_confirmation_field_options[:if]
+ end
+
+ def test_password_not_required_on_create
+ user = User.new
+ user.login = "sweet"
+ user.email = "a@a.com"
+ user.openid_identifier = "https://me.yahoo.com/a/9W0FJjRj0o981TMSs0vqVxPdmMUVOQ--"
+ assert !user.save # because we are redirecting, the user was NOT saved
+ assert redirecting_to_yahoo?
+ end
+
+ def test_password_required_on_create
+ user = User.new
+ user.login = "sweet"
+ user.email = "a@a.com"
+ assert !user.save
+ assert user.errors.on(:password)
+ assert user.errors.on(:password_confirmation)
+ end
+
+ def test_password_not_required_on_update
+ ben = users(:ben)
+ assert_nil ben.crypted_password
+ assert ben.save
+ end
+
+ def test_password__required_on_update
+ ben = users(:ben)
+ ben.openid_identifier = nil
+ assert_nil ben.crypted_password
+ assert !ben.save
+ assert ben.errors.on(:password)
+ assert ben.errors.on(:password_confirmation)
+ end
+
+ def test_validates_uniqueness_of_openid_identifier
+ u = User.new(:openid_identifier => "bens_identifier")
+ assert !u.valid?
+ assert u.errors.on(:openid_identifier)
+ end
+
+ def test_setting_openid_identifier_changed_persistence_token
+ ben = users(:ben)
+ old_persistence_token = ben.persistence_token
+ ben.openid_identifier = "new"
+ assert_not_equal old_persistence_token, ben.persistence_token
+ end
+
+ def test_invalid_openid_identifier
+ u = User.new(:openid_identifier => "%")
+ assert !u.valid?
+ assert u.errors.on(:openid_identifier)
+ end
+
+ def test_blank_openid_identifer_gets_set_to_nil
+ u = User.new(:openid_identifier => "")
+ assert_nil u.openid_identifier
+ end
+
+ def test_updating_with_openid
+ ben = users(:ben)
+ ben.openid_identifier = "https://me.yahoo.com/a/9W0FJjRj0o981TMSs0vqVxPdmMUVOQ--"
+ assert !ben.save # because we are redirecting
+ assert redirecting_to_yahoo?
+ end
+
+ def test_updating_without_openid
+ ben = users(:ben)
+ ben.openid_identifier = nil
+ ben.password = "test"
+ ben.password_confirmation = "test"
+ assert ben.save
+ assert !redirecting_to_yahoo?
+ end
+
+ def test_updating_without_validation
+ ben = users(:ben)
+ ben.openid_identifier = "https://me.yahoo.com/a/9W0FJjRj0o981TMSs0vqVxPdmMUVOQ--"
+ assert ben.save(false)
+ assert !redirecting_to_yahoo?
+ end
+end
View
9 test/fixtures/users.yml
@@ -0,0 +1,9 @@
+ben:
+ login: bjohnson
+ persistence_token: 6cde0674657a8a313ce952df979de2830309aa4c11ca65805dd00bfdc65dbcc2f5e36718660a1d2e68c1a08c276d996763985d2f06fd3d076eb7bc4d97b1e317
+ single_access_token: <%= Authlogic::Random.friendly_token %>
+ perishable_token: <%= Authlogic::Random.friendly_token %>
+ openid_identifier: bens_identifier
+ email: bjohnson@binarylogic.com
+ first_name: Ben
+ last_name: Johnson
View
35 test/libs/open_id_authentication/CHANGELOG
@@ -0,0 +1,35 @@
+* Fake HTTP method from OpenID server since they only support a GET. Eliminates the need to set an extra route to match the server's reply. [Josh Peek]
+
+* OpenID 2.0 recommends that forms should use the field name "openid_identifier" rather than "openid_url" [Josh Peek]
+
+* Return open_id_response.display_identifier to the application instead of .endpoints.claimed_id. [nbibler]
+
+* Add Timeout protection [Rick]
+
+* An invalid identity url passed through authenticate_with_open_id will no longer raise an InvalidOpenId exception. Instead it will return Result[:missing] to the completion block.
+
+* Allow a return_to option to be used instead of the requested url [Josh Peek]
+
+* Updated plugin to use Ruby OpenID 2.x.x [Josh Peek]
+
+* Tied plugin to ruby-openid 1.1.4 gem until we can make it compatible with 2.x [DHH]
+
+* Use URI instead of regexps to normalize the URL and gain free, better matching #8136 [dkubb]
+
+* Allow -'s in #normalize_url [Rick]
+
+* remove instance of mattr_accessor, it was breaking tests since they don't load ActiveSupport. Fix Timeout test [Rick]
+
+* Throw a InvalidOpenId exception instead of just a RuntimeError when the URL can't be normalized [DHH]
+
+* Just use the path for the return URL, so extra query parameters don't interfere [DHH]
+
+* Added a new default database-backed store after experiencing trouble with the filestore on NFS. The file store is still available as an option [DHH]
+
+* Added normalize_url and applied it to all operations going through the plugin [DHH]
+
+* Removed open_id? as the idea of using the same input box for both OpenID and username has died -- use using_open_id? instead (which checks for the presence of params[:openid_url] by default) [DHH]
+
+* Added OpenIdAuthentication::Result to make it easier to deal with default situations where you don't care to do something particular for each error state [DHH]
+
+* Stop relying on root_url being defined, we can just grab the current url instead [DHH]
View
231 test/libs/open_id_authentication/README
@@ -0,0 +1,231 @@
+OpenIdAuthentication
+====================
+
+Provides a thin wrapper around the excellent ruby-openid gem from JanRan. Be sure to install that first:
+
+ gem install ruby-openid
+
+To understand what OpenID is about and how it works, it helps to read the documentation for lib/openid/consumer.rb
+from that gem.
+
+The specification used is http://openid.net/specs/openid-authentication-2_0.html.
+
+
+Prerequisites
+=============
+
+OpenID authentication uses the session, so be sure that you haven't turned that off. It also relies on a number of
+database tables to store the authentication keys. So you'll have to run the migration to create these before you get started:
+
+ rake open_id_authentication:db:create
+
+Or, use the included generators to install or upgrade:
+
+ ./script/generate open_id_authentication_tables MigrationName
+ ./script/generate upgrade_open_id_authentication_tables MigrationName
+
+Alternatively, you can use the file-based store, which just relies on on tmp/openids being present in RAILS_ROOT. But be aware that this store only works if you have a single application server. And it's not safe to use across NFS. It's recommended that you use the database store if at all possible. To use the file-based store, you'll also have to add this line to your config/environment.rb:
+
+ OpenIdAuthentication.store = :file
+
+This particular plugin also relies on the fact that the authentication action allows for both POST and GET operations.
+If you're using RESTful authentication, you'll need to explicitly allow for this in your routes.rb.
+
+The plugin also expects to find a root_url method that points to the home page of your site. You can accomplish this by using a root route in config/routes.rb:
+
+ map.root :controller => 'articles'
+
+This plugin relies on Rails Edge revision 6317 or newer.
+
+
+Example
+=======
+
+This example is just to meant to demonstrate how you could use OpenID authentication. You might well want to add
+salted hash logins instead of plain text passwords and other requirements on top of this. Treat it as a starting point,
+not a destination.
+
+Note that the User model referenced in the simple example below has an 'identity_url' attribute. You will want to add the same or similar field to whatever
+model you are using for authentication.
+
+Also of note is the following code block used in the example below:
+
+ authenticate_with_open_id do |result, identity_url|
+ ...
+ end
+
+In the above code block, 'identity_url' will need to match user.identity_url exactly. 'identity_url' will be a string in the form of 'http://example.com' -
+If you are storing just 'example.com' with your user, the lookup will fail.
+
+There is a handy method in this plugin called 'normalize_url' that will help with validating OpenID URLs.
+
+ OpenIdAuthentication.normalize_url(user.identity_url)
+
+The above will return a standardized version of the OpenID URL - the above called with 'example.com' will return 'http://example.com/'
+It will also raise an InvalidOpenId exception if the URL is determined to not be valid.
+Use the above code in your User model and validate OpenID URLs before saving them.
+
+config/routes.rb
+
+ map.root :controller => 'articles'
+ map.resource :session
+
+
+app/views/sessions/new.erb
+
+ <% form_tag(session_url) do %>
+ <p>
+ <label for="name">Username:</label>
+ <%= text_field_tag "name" %>
+ </p>
+
+ <p>
+ <label for="password">Password:</label>
+ <%= password_field_tag %>
+ </p>
+
+ <p>
+ ...or use:
+ </p>
+
+ <p>
+ <label for="openid_identifier">OpenID:</label>
+ <%= text_field_tag "openid_identifier" %>
+ </p>
+
+ <p>
+ <%= submit_tag 'Sign in', :disable_with => "Signing in&hellip;" %>
+ </p>
+ <% end %>
+
+app/controllers/sessions_controller.rb
+ class SessionsController < ApplicationController
+ def create
+ if using_open_id?
+ open_id_authentication
+ else
+ password_authentication(params[:name], params[:password])
+ end
+ end
+
+
+ protected
+ def password_authentication(name, password)
+ if @current_user = @account.users.authenticate(params[:name], params[:password])
+ successful_login
+ else
+ failed_login "Sorry, that username/password doesn't work"
+ end
+ end
+
+ def open_id_authentication
+ authenticate_with_open_id do |result, identity_url|
+ if result.successful?
+ if @current_user = @account.users.find_by_identity_url(identity_url)
+ successful_login
+ else
+ failed_login "Sorry, no user by that identity URL exists (#{identity_url})"
+ end
+ else
+ failed_login result.message
+ end
+ end
+ end
+
+
+ private
+ def successful_login
+ session[:user_id] = @current_user.id
+ redirect_to(root_url)
+ end
+
+ def failed_login(message)
+ flash[:error] = message
+ redirect_to(new_session_url)
+ end
+ end
+
+
+
+If you're fine with the result messages above and don't need individual logic on a per-failure basis,
+you can collapse the case into a mere boolean:
+
+ def open_id_authentication
+ authenticate_with_open_id do |result, identity_url|
+ if result.successful? && @current_user = @account.users.find_by_identity_url(identity_url)
+ successful_login
+ else
+ failed_login(result.message || "Sorry, no user by that identity URL exists (#{identity_url})")
+ end
+ end
+ end
+
+
+Simple Registration OpenID Extension
+====================================
+
+Some OpenID Providers support this lightweight profile exchange protocol. See more: http://www.openidenabled.com/openid/simple-registration-extension
+
+You can support it in your app by changing #open_id_authentication
+
+ def open_id_authentication(identity_url)
+ # Pass optional :required and :optional keys to specify what sreg fields you want.
+ # Be sure to yield registration, a third argument in the #authenticate_with_open_id block.
+ authenticate_with_open_id(identity_url,
+ :required => [ :nickname, :email ],
+ :optional => :fullname) do |result, identity_url, registration|
+ case result.status
+ when :missing
+ failed_login "Sorry, the OpenID server couldn't be found"
+ when :invalid
+ failed_login "Sorry, but this does not appear to be a valid OpenID"
+ when :canceled
+ failed_login "OpenID verification was canceled"
+ when :failed
+ failed_login "Sorry, the OpenID verification failed"
+ when :successful
+ if @current_user = @account.users.find_by_identity_url(identity_url)
+ assign_registration_attributes!(registration)
+
+ if current_user.save
+ successful_login
+ else
+ failed_login "Your OpenID profile registration failed: " +
+ @current_user.errors.full_messages.to_sentence
+ end
+ else
+ failed_login "Sorry, no user by that identity URL exists"
+ end
+ end
+ end
+ end
+
+ # registration is a hash containing the valid sreg keys given above
+ # use this to map them to fields of your user model
+ def assign_registration_attributes!(registration)
+ model_to_registration_mapping.each do |model_attribute, registration_attribute|
+ unless registration[registration_attribute].blank?
+ @current_user.send("#{model_attribute}=", registration[registration_attribute])
+ end
+ end
+ end
+
+ def model_to_registration_mapping
+ { :login => 'nickname', :email => 'email', :display_name => 'fullname' }
+ end
+
+Attribute Exchange OpenID Extension
+===================================
+
+Some OpenID providers also support the OpenID AX (attribute exchange) protocol for exchanging identity information between endpoints. See more: http://openid.net/specs/openid-attribute-exchange-1_0.html
+
+Accessing AX data is very similar to the Simple Registration process, described above -- just add the URI identifier for the AX field to your :optional or :required parameters. For example:
+
+ authenticate_with_open_id(identity_url,
+ :required => [ :email, 'http://schema.openid.net/birthDate' ]) do |result, identity_url, registration|
+
+This would provide the sreg data for :email, and the AX data for 'http://schema.openid.net/birthDate'
+
+
+
+Copyright (c) 2007 David Heinemeier Hansson, released under the MIT license
View
22 test/libs/open_id_authentication/Rakefile
@@ -0,0 +1,22 @@
+require 'rake'
+require 'rake/testtask'
+require 'rake/rdoctask'
+
+desc 'Default: run unit tests.'
+task :default => :test
+
+desc 'Test the open_id_authentication plugin.'
+Rake::TestTask.new(:test) do |t|
+ t.libs << 'lib'
+ t.pattern = 'test/**/*_test.rb'
+ t.verbose = true
+end
+
+desc 'Generate documentation for the open_id_authentication plugin.'
+Rake::RDocTask.new(:rdoc) do |rdoc|
+ rdoc.rdoc_dir = 'rdoc'
+ rdoc.title = 'OpenIdAuthentication'
+ rdoc.options << '--line-numbers' << '--inline-source'
+ rdoc.rdoc_files.include('README')
+ rdoc.rdoc_files.include('lib/**/*.rb')
+end
View
11 ...en_id_authentication/generators/open_id_authentication_tables/open_id_authentication_tables_generator.rb
@@ -0,0 +1,11 @@
+class OpenIdAuthenticationTablesGenerator < Rails::Generator::NamedBase
+ def initialize(runtime_args, runtime_options = {})
+ super
+ end
+
+ def manifest
+ record do |m|
+ m.migration_template 'migration.rb', 'db/migrate'
+ end
+ end
+end
View
20 test/libs/open_id_authentication/generators/open_id_authentication_tables/templates/migration.rb
@@ -0,0 +1,20 @@
+class <%= class_name %> < ActiveRecord::Migration
+ def self.up
+ create_table :open_id_authentication_associations, :force => true do |t|
+ t.integer :issued, :lifetime
+ t.string :handle, :assoc_type
+ t.binary :server_url, :secret
+ end
+
+ create_table :open_id_authentication_nonces, :force => true do |t|
+ t.integer :timestamp, :null => false
+ t.string :server_url, :null => true
+ t.string :salt, :null => false
+ end
+ end
+
+ def self.down
+ drop_table :open_id_authentication_associations
+ drop_table :open_id_authentication_nonces
+ end
+end
View
26 test/libs/open_id_authentication/generators/upgrade_open_id_authentication_tables/templates/migration.rb
@@ -0,0 +1,26 @@
+class <%= class_name %> < ActiveRecord::Migration
+ def self.up
+ drop_table :open_id_authentication_settings
+ drop_table :open_id_authentication_nonces
+
+ create_table :open_id_authentication_nonces, :force => true do |t|
+ t.integer :timestamp, :null => false
+ t.string :server_url, :null => true
+ t.string :salt, :null => false
+ end
+ end
+
+ def self.down
+ drop_table :open_id_authentication_nonces
+
+ create_table :open_id_authentication_nonces, :force => true do |t|
+ t.integer :created
+ t.string :nonce
+ end
+
+ create_table :open_id_authentication_settings, :force => true do |t|
+ t.string :setting
+ t.binary :value
+ end
+ end
+end
View
11 ...tion/generators/upgrade_open_id_authentication_tables/upgrade_open_id_authentication_tables_generator.rb
@@ -0,0 +1,11 @@
+class UpgradeOpenIdAuthenticationTablesGenerator < Rails::Generator::NamedBase
+ def initialize(runtime_args, runtime_options = {})
+ super
+ end
+
+ def manifest
+ record do |m|
+ m.migration_template 'migration.rb', 'db/migrate'
+ end
+ end
+end
View
18 test/libs/open_id_authentication/init.rb
@@ -0,0 +1,18 @@
+if config.respond_to?(:gems)
+ config.gem 'ruby-openid', :lib => 'openid', :version => '>=2.0.4'
+else
+ begin
+ require 'openid'
+ rescue LoadError
+ begin
+ gem 'ruby-openid', '>=2.0.4'
+ rescue Gem::LoadError
+ puts "Install the ruby-openid gem to enable OpenID support"
+ end
+ end
+end
+
+config.to_prepare do
+ OpenID::Util.logger = Rails.logger
+ ActionController::Base.send :include, OpenIdAuthentication
+end
View
244 test/libs/open_id_authentication/lib/open_id_authentication.rb
@@ -0,0 +1,244 @@
+require 'uri'
+require 'openid/extensions/sreg'
+require 'openid/extensions/ax'
+require 'openid/store/filesystem'
+
+require File.dirname(__FILE__) + '/open_id_authentication/association'
+require File.dirname(__FILE__) + '/open_id_authentication/nonce'
+require File.dirname(__FILE__) + '/open_id_authentication/db_store'
+require File.dirname(__FILE__) + '/open_id_authentication/mem_cache_store'
+require File.dirname(__FILE__) + '/open_id_authentication/request'
+require File.dirname(__FILE__) + '/open_id_authentication/timeout_fixes' if OpenID::VERSION == "2.0.4"
+
+module OpenIdAuthentication
+ OPEN_ID_AUTHENTICATION_DIR = RAILS_ROOT + "/tmp/openids"
+
+ def self.store
+ @@store
+ end
+
+ def self.store=(*store_option)
+ store, *parameters = *([ store_option ].flatten)
+
+ @@store = case store
+ when :db
+ OpenIdAuthentication::DbStore.new
+ when :mem_cache
+ OpenIdAuthentication::MemCacheStore.new(*parameters)
+ when :file
+ OpenID::Store::Filesystem.new(OPEN_ID_AUTHENTICATION_DIR)
+ else
+ raise "Unknown store: #{store}"
+ end
+ end
+
+ self.store = :db
+
+ class InvalidOpenId < StandardError
+ end
+
+ class Result
+ ERROR_MESSAGES = {
+ :missing => "Sorry, the OpenID server couldn't be found",
+ :invalid => "Sorry, but this does not appear to be a valid OpenID",
+ :canceled => "OpenID verification was canceled",
+ :failed => "OpenID verification failed",
+ :setup_needed => "OpenID verification needs setup"
+ }
+
+ def self.[](code)
+ new(code)
+ end
+
+ def initialize(code)
+ @code = code
+ end
+
+ def status
+ @code
+ end
+
+ ERROR_MESSAGES.keys.each { |state| define_method("#{state}?") { @code == state } }
+
+ def successful?
+ @code == :successful
+ end
+
+ def unsuccessful?
+ ERROR_MESSAGES.keys.include?(@code)
+ end
+
+ def message
+ ERROR_MESSAGES[@code]
+ end
+ end
+
+ # normalizes an OpenID according to http://openid.net/specs/openid-authentication-2_0.html#normalization
+ def self.normalize_identifier(identifier)
+ # clean up whitespace
+ identifier = identifier.to_s.strip
+
+ # if an XRI has a prefix, strip it.
+ identifier.gsub!(/xri:\/\//i, '')
+
+ # dodge XRIs -- TODO: validate, don't just skip.
+ unless ['=', '@', '+', '$', '!', '('].include?(identifier.at(0))
+ # does it begin with http? if not, add it.
+ identifier = "http://#{identifier}" unless identifier =~ /^http/i
+
+ # strip any fragments
+ identifier.gsub!(/\#(.*)$/, '')
+
+ begin
+ uri = URI.parse(identifier)
+ uri.scheme = uri.scheme.downcase # URI should do this
+ identifier = uri.normalize.to_s
+ rescue URI::InvalidURIError
+ raise InvalidOpenId.new("#{identifier} is not an OpenID identifier")
+ end
+ end
+
+ return identifier
+ end
+
+ # deprecated for OpenID 2.0, where not all OpenIDs are URLs
+ def self.normalize_url(url)
+ ActiveSupport::Deprecation.warn "normalize_url has been deprecated, use normalize_identifier instead"
+ self.normalize_identifier(url)
+ end
+
+ protected
+ def normalize_url(url)
+ OpenIdAuthentication.normalize_url(url)
+ end
+
+ def normalize_identifier(url)
+ OpenIdAuthentication.normalize_identifier(url)
+ end
+
+ # The parameter name of "openid_identifier" is used rather than the Rails convention "open_id_identifier"
+ # because that's what the specification dictates in order to get browser auto-complete working across sites
+ def using_open_id?(identity_url = nil) #:doc:
+ identity_url ||= params[:openid_identifier] || params[:openid_url]
+ !identity_url.blank? || params[:open_id_complete]
+ end
+
+ def authenticate_with_open_id(identity_url = nil, options = {}, &block) #:doc:
+ identity_url ||= params[:openid_identifier] || params[:openid_url]
+
+ if params[:open_id_complete].nil?
+ begin_open_id_authentication(identity_url, options, &block)
+ else
+ complete_open_id_authentication(&block)
+ end
+ end
+
+ private
+ def begin_open_id_authentication(identity_url, options = {})
+ identity_url = normalize_identifier(identity_url)
+ return_to = options.delete(:return_to)
+ method = options.delete(:method)
+
+ options[:required] ||= [] # reduces validation later
+ options[:optional] ||= []
+
+ open_id_request = open_id_consumer.begin(identity_url)
+ add_simple_registration_fields(open_id_request, options)
+ add_ax_fields(open_id_request, options)
+
+ redirect_to(open_id_redirect_url(open_id_request, return_to, method))
+ rescue OpenIdAuthentication::InvalidOpenId => e
+ yield Result[:invalid], identity_url, nil
+ rescue OpenID::OpenIDError, Timeout::Error => e
+ logger.error("[OPENID] #{e}")
+ yield Result[:missing], identity_url, nil
+ end
+
+ def complete_open_id_authentication
+ params_with_path = params.reject { |key, value| request.path_parameters[key] }
+ params_with_path.delete(:format)
+ open_id_response = timeout_protection_from_identity_server { open_id_consumer.complete(params_with_path, requested_url) }
+ identity_url = normalize_identifier(open_id_response.display_identifier) if open_id_response.display_identifier
+
+ case open_id_response.status
+ when OpenID::Consumer::SUCCESS
+ profile_data = {}
+
+ # merge the SReg data and the AX data into a single hash of profile data
+ [ OpenID::SReg::Response, OpenID::AX::FetchResponse ].each do |data_response|
+ if data_response.from_success_response( open_id_response )
+ profile_data.merge! data_response.from_success_response( open_id_response ).data
+ end
+ end
+
+ yield Result[:successful], identity_url, profile_data
+ when OpenID::Consumer::CANCEL
+ yield Result[:canceled], identity_url, nil
+ when OpenID::Consumer::FAILURE
+ yield Result[:failed], identity_url, nil
+ when OpenID::Consumer::SETUP_NEEDED
+ yield Result[:setup_needed], open_id_response.setup_url, nil
+ end
+ end
+
+ def open_id_consumer
+ OpenID::Consumer.new(session, OpenIdAuthentication.store)
+ end
+
+ def add_simple_registration_fields(open_id_request, fields)
+ sreg_request = OpenID::SReg::Request.new
+
+ # filter out AX identifiers (URIs)
+ required_fields = fields[:required].collect { |f| f.to_s unless f =~ /^https?:\/\// }.compact
+ optional_fields = fields[:optional].collect { |f| f.to_s unless f =~ /^https?:\/\// }.compact
+
+ sreg_request.request_fields(required_fields, true) unless required_fields.blank?
+ sreg_request.request_fields(optional_fields, false) unless optional_fields.blank?
+ sreg_request.policy_url = fields[:policy_url] if fields[:policy_url]
+ open_id_request.add_extension(sreg_request)
+ end
+
+ def add_ax_fields( open_id_request, fields )
+ ax_request = OpenID::AX::FetchRequest.new
+
+ # look through the :required and :optional fields for URIs (AX identifiers)
+ fields[:required].each do |f|
+ next unless f =~ /^https?:\/\//
+ ax_request.add( OpenID::AX::AttrInfo.new( f, nil, true ) )
+ end
+
+ fields[:optional].each do |f|
+ next unless f =~ /^https?:\/\//
+ ax_request.add( OpenID::AX::AttrInfo.new( f, nil, false ) )
+ end
+
+ open_id_request.add_extension( ax_request )
+ end
+
+ def open_id_redirect_url(open_id_request, return_to = nil, method = nil)
+ open_id_request.return_to_args['_method'] = (method || request.method).to_s
+ open_id_request.return_to_args['open_id_complete'] = '1'
+ open_id_request.redirect_url(root_url, return_to || requested_url)
+ end
+
+ def requested_url
+ relative_url_root = self.class.respond_to?(:relative_url_root) ?
+ self.class.relative_url_root.to_s :
+ request.relative_url_root
+ "#{request.protocol}#{request.host_with_port}#{ActionController::Base.relative_url_root}#{request.path}"
+ end
+
+ def timeout_protection_from_identity_server
+ yield
+ rescue Timeout::Error
+ Class.new do
+ def status
+ OpenID::FAILURE
+ end
+
+ def msg
+ "Identity server timed out"
+ end
+ end.new
+ end
+end
View
9 test/libs/open_id_authentication/lib/open_id_authentication/association.rb
@@ -0,0 +1,9 @@
+module OpenIdAuthentication
+ class Association < ActiveRecord::Base
+ set_table_name :open_id_authentication_associations
+
+ def from_record
+ OpenID::Association.new(handle, secret, issued, lifetime, assoc_type)
+ end
+ end
+end
View
55 test/libs/open_id_authentication/lib/open_id_authentication/db_store.rb
@@ -0,0 +1,55 @@
+require 'openid/store/interface'
+
+module OpenIdAuthentication
+ class DbStore < OpenID::Store::Interface
+ def self.cleanup_nonces
+ now = Time.now.to_i
+ Nonce.delete_all(["timestamp > ? OR timestamp < ?", now + OpenID::Nonce.skew, now - OpenID::Nonce.skew])
+ end
+
+ def self.cleanup_associations
+ now = Time.now.to_i
+ Association.delete_all(['issued + lifetime > ?',now])
+ end
+
+ 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,
+ :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
+ end
+end
View
73 test/libs/open_id_authentication/lib/open_id_authentication/mem_cache_store.rb
@@ -0,0 +1,73 @@
+require 'digest/sha1'
+require 'openid/store/interface'
+
+module OpenIdAuthentication
+ class MemCacheStore < OpenID::Store::Interface
+ def initialize(*addresses)
+ @connection = ActiveSupport::Cache::MemCacheStore.new(addresses)
+ end
+
+ def store_association(server_url, assoc)
+ server_key = association_server_key(server_url)
+ assoc_key = association_key(server_url, assoc.handle)
+
+ assocs = @connection.read(server_key) || {}
+ assocs[assoc.issued] = assoc_key
+
+ @connection.write(server_key, assocs)
+ @connection.write(assoc_key, assoc, :expires_in => assoc.lifetime)
+ end
+
+ def get_association(server_url, handle = nil)
+ if handle
+ @connection.read(association_key(server_url, handle))
+ else
+ server_key = association_server_key(server_url)
+ assocs = @connection.read(server_key)
+ return if assocs.nil?
+
+ last_key = assocs[assocs.keys.sort.last]
+ @connection.read(last_key)
+ end
+ end
+
+ def remove_association(server_url, handle)
+ server_key = association_server_key(server_url)
+ assoc_key = association_key(server_url, handle)
+ assocs = @connection.read(server_key)
+
+ return false unless assocs && assocs.has_value?(assoc_key)
+
+ assocs = assocs.delete_if { |key, value| value == assoc_key }
+
+ @connection.write(server_key, assocs)
+ @connection.delete(assoc_key)
+
+ return true
+ end
+
+ def use_nonce(server_url, timestamp, salt)
+ return false if @connection.read(nonce_key(server_url, salt))
+ return false if (timestamp - Time.now.to_i).abs > OpenID::Nonce.skew
+ @connection.write(nonce_key(server_url, salt), timestamp, :expires_in => OpenID::Nonce.skew)
+ return true
+ end
+
+ private
+ def association_key(server_url, handle = nil)
+ "openid_association_#{digest(server_url)}_#{digest(handle)}"
+ end
+
+ def association_server_key(server_url)
+ "openid_association_server_#{digest(server_url)}"
+ end
+
+ def nonce_key(server_url, salt)
+ "openid_nonce_#{digest(server_url)}_#{digest(salt)}"
+ end
+
+ def digest(text)
+ Digest::SHA1.hexdigest(text)
+ end
+ end
+end
View
5 test/libs/open_id_authentication/lib/open_id_authentication/nonce.rb
@@ -0,0 +1,5 @@
+module OpenIdAuthentication
+ class Nonce < ActiveRecord::Base
+ set_table_name :open_id_authentication_nonces
+ end
+end
View
23 test/libs/open_id_authentication/lib/open_id_authentication/request.rb
@@ -0,0 +1,23 @@
+module OpenIdAuthentication
+ module Request
+ def self.included(base)
+ base.alias_method_chain :request_method, :openid
+ end
+
+ def request_method_with_openid
+ if !parameters[:_method].blank? && parameters[:open_id_complete] == '1'
+ parameters[:_method].to_sym
+ else
+ request_method_without_openid
+ end
+ end
+ end
+end
+
+# In Rails 2.3, the request object has been renamed
+# from AbstractRequest to Request
+if defined? ActionController::Request
+ ActionController::Request.send :include, OpenIdAuthentication::Request
+else
+ ActionController::AbstractRequest.send :include, OpenIdAuthentication::Request
+end
View
20 test/libs/open_id_authentication/lib/open_id_authentication/timeout_fixes.rb
@@ -0,0 +1,20 @@
+# http://trac.openidenabled.com/trac/ticket/156
+module OpenID
+ @@timeout_threshold = 20
+
+ def self.timeout_threshold
+ @@timeout_threshold
+ end
+
+ def self.timeout_threshold=(value)
+ @@timeout_threshold = value
+ end
+
+ class StandardFetcher
+ def make_http(uri)
+ http = @proxy.new(uri.host, uri.port)
+ http.read_timeout = http.open_timeout = OpenID.timeout_threshold
+ http
+ end
+ end
+end
View
30 test/libs/open_id_authentication/tasks/open_id_authentication_tasks.rake
@@ -0,0 +1,30 @@
+namespace :open_id_authentication do
+ namespace :db do
+ desc "Creates authentication tables for use with OpenIdAuthentication"
+ task :create => :environment do
+ generate_migration(["open_id_authentication_tables", "add_open_id_authentication_tables"])
+ end
+
+ desc "Upgrade authentication tables from ruby-openid 1.x.x to 2.x.x"
+ task :upgrade => :environment do
+ generate_migration(["upgrade_open_id_authentication_tables", "upgrade_open_id_authentication_tables"])
+ end
+
+ def generate_migration(args)
+ require 'rails_generator'
+ require 'rails_generator/scripts/generate'
+
+ if ActiveRecord::Base.connection.supports_migrations?
+ Rails::Generator::Scripts::Generate.new.run(args)
+ else
+ raise "Task unavailable to this database (no migration support)"
+ end
+ end
+
+ desc "Clear the authentication tables"
+ task :clear => :environment do
+ OpenIdAuthentication::DbStore.cleanup_nonces
+ OpenIdAuthentication::DbStore.cleanup_associations
+ end
+ end
+end
View
151 test/libs/open_id_authentication/test/mem_cache_store_test.rb
@@ -0,0 +1,151 @@
+require File.dirname(__FILE__) + '/test_helper'
+require File.dirname(__FILE__) + '/../lib/open_id_authentication/mem_cache_store'
+
+# Mock MemCacheStore with MemoryStore for testing
+class OpenIdAuthentication::MemCacheStore < OpenID::Store::Interface
+ def initialize(*addresses)
+ @connection = ActiveSupport::Cache::MemoryStore.new
+ end
+end
+
+class MemCacheStoreTest < Test::Unit::TestCase
+ ALLOWED_HANDLE = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~'
+
+ def setup
+ @store = OpenIdAuthentication::MemCacheStore.new
+ end
+
+ def test_store
+ server_url = "http://www.myopenid.com/openid"
+ assoc = gen_assoc(0)
+
+ # Make sure that a missing association returns no result
+ assert_retrieve(server_url)
+
+ # Check that after storage, getting returns the same result
+ @store.store_association(server_url, assoc)
+ assert_retrieve(server_url, nil, assoc)
+
+ # more than once
+ assert_retrieve(server_url, nil, assoc)
+
+ # Storing more than once has no ill effect
+ @store.store_association(server_url, assoc)
+ assert_retrieve(server_url, nil, assoc)
+
+ # Removing an association that does not exist returns not present
+ assert_remove(server_url, assoc.handle + 'x', false)
+
+ # Removing an association that does not exist returns not present
+ assert_remove(server_url + 'x', assoc.handle, false)
+
+ # Removing an association that is present returns present
+ assert_remove(server_url, assoc.handle, true)
+
+ # but not present on subsequent calls
+ assert_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(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.
+ assert_retrieve(server_url, nil, assoc2)
+
+ # We can still retrieve the older association
+ assert_retrieve(server_url, assoc.handle, assoc)
+
+ # Plus we can retrieve the association with the later expiration
+ # explicitly
+ assert_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(2, 100)
+ @store.store_association(server_url, assoc3)
+
+ assert_retrieve(server_url, nil, assoc3)
+ assert_retrieve(server_url, assoc.handle, assoc)
+ assert_retrieve(server_url, assoc2.handle, assoc2)
+ assert_retrieve(server_url, assoc3.handle, assoc3)
+
+ assert_remove(server_url, assoc2.handle, true)
+
+ assert_retrieve(server_url, nil, assoc3)
+ assert_retrieve(server_url, assoc.handle, assoc)
+ assert_retrieve(server_url, assoc2.handle, nil)
+ assert_retrieve(server_url, assoc3.handle, assoc3)
+
+ assert_remove(server_url, assoc2.handle, false)
+ assert_remove(server_url, assoc3.handle, true)
+
+ assert_retrieve(server_url, nil, assoc)
+ assert_retrieve(server_url, assoc.handle, assoc)
+ assert_retrieve(server_url, assoc2.handle, nil)
+ assert_retrieve(server_url, assoc3.handle, nil)
+
+ assert_remove(server_url, assoc2.handle, false)
+ assert_remove(server_url, assoc.handle, true)
+ assert_remove(server_url, assoc3.handle, false)
+
+ assert_retrieve(server_url, nil, nil)
+ assert_retrieve(server_url, assoc.handle, nil)
+ assert_retrieve(server_url, assoc2.handle, nil)
+ assert_retrieve(server_url, assoc3.handle, nil)
+
+ assert_remove(server_url, assoc2.handle, false)
+ assert_remove(server_url, assoc.handle, false)
+ assert_remove(server_url, assoc3.handle, false)
+ end
+
+ def test_nonce
+ server_url = "http://www.myopenid.com/openid"
+
+ [server_url, ''].each do |url|
+ nonce1 = OpenID::Nonce::mk_nonce
+
+ assert_nonce(nonce1, true, url, "#{url}: nonce allowed by default")
+ assert_nonce(nonce1, false, url, "#{url}: nonce not allowed twice")
+ assert_nonce(nonce1, false, url, "#{url}: nonce not allowed third time")
+
+ # old nonces shouldn't pass
+ old_nonce = OpenID::Nonce::mk_nonce(3600)
+ assert_nonce(old_nonce, false, url, "Old nonce #{old_nonce.inspect} passed")
+ end
+ end
+
+ private
+ def gen_assoc(issued, lifetime = 600)
+ secret = OpenID::CryptUtil.random_string(20, nil)
+ handle = OpenID::CryptUtil.random_string(128, ALLOWED_HANDLE)
+ OpenID::Association.new(handle, secret, Time.now + issued, lifetime, 'HMAC-SHA1')
+ end
+
+ def assert_retrieve(url, handle = nil, expected = nil)
+ assoc = @store.get_association(url, handle)
+
+ if expected.nil?
+ assert_nil(assoc)
+ else
+ assert_equal(expected, assoc)
+ assert_equal(expected.handle, assoc.handle)
+ assert_equal(expected.secret, assoc.secret)
+ end
+ end
+
+ def assert_remove(url, handle, expected)
+ present = @store.remove_association(url, handle)
+ assert_equal(expected, present)
+ end
+
+ def assert_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
+end
View
32 test/libs/open_id_authentication/test/normalize_test.rb
@@ -0,0 +1,32 @@
+require File.dirname(__FILE__) + '/test_helper'
+
+class NormalizeTest < Test::Unit::TestCase
+ include OpenIdAuthentication
+
+ NORMALIZATIONS = {
+ "openid.aol.com/nextangler" => "http://openid.aol.com/nextangler",
+ "http://openid.aol.com/nextangler" => "http://openid.aol.com/nextangler",
+ "https://openid.aol.com/nextangler" => "https://openid.aol.com/nextangler",
+ "HTTP://OPENID.AOL.COM/NEXTANGLER" => "http://openid.aol.com/NEXTANGLER",
+ "HTTPS://OPENID.AOL.COM/NEXTANGLER" => "https://openid.aol.com/NEXTANGLER",
+ "loudthinking.com" => "http://loudthinking.com/",
+ "http://loudthinking.com" => "http://loudthinking.com/",
+ "http://loudthinking.com:80" => "http://loudthinking.com/",
+ "https://loudthinking.com:443" => "https://loudthinking.com/",
+ "http://loudthinking.com:8080" => "http://loudthinking.com:8080/",
+ "techno-weenie.net" => "http://techno-weenie.net/",
+ "http://techno-weenie.net" => "http://techno-weenie.net/",
+ "http://techno-weenie.net " => "http://techno-weenie.net/",
+ "=name" => "=name"
+ }
+
+ def test_normalizations
+ NORMALIZATIONS.each do |from, to|
+ assert_equal to, normalize_identifier(from)
+ end
+ end
+
+ def test_broken_open_id
+ assert_raises(InvalidOpenId) { normalize_identifier(nil) }
+ end
+end
View
46 test/libs/open_id_authentication/test/open_id_authentication_test.rb
@@ -0,0 +1,46 @@
+require File.dirname(__FILE__) + '/test_helper'
+
+class OpenIdAuthenticationTest < Test::Unit::TestCase
+ def setup
+ @controller = Class.new do
+ include OpenIdAuthentication
+ def params() {} end
+ end.new
+ end
+
+ def test_authentication_should_fail_when_the_identity_server_is_missing
+ open_id_consumer = mock()
+ open_id_consumer.expects(:begin).raises(OpenID::OpenIDError)
+ @controller.expects(:open_id_consumer).returns(open_id_consumer)
+ @controller.expects(:logger).returns(mock(:error => true))
+
+ @controller.send(:authenticate_with_open_id, "http://someone.example.com") do |result, identity_url|
+ assert result.missing?
+ assert_equal "Sorry, the OpenID server couldn't be found", result.message
+ end
+ end
+
+ def test_authentication_should_be_invalid_when_the_identity_url_is_invalid
+ @controller.send(:authenticate_with_open_id, "!") do |result, identity_url|
+ assert result.invalid?, "Result expected to be invalid but was not"
+ assert_equal "Sorry, but this does not appear to be a valid OpenID", result.message
+ end
+ end
+
+ def test_authentication_should_fail_when_the_identity_server_times_out
+ open_id_consumer = mock()
+ open_id_consumer.expects(:begin).raises(Timeout::Error, "Identity Server took too long.")
+ @controller.expects(:open_id_consumer).returns(open_id_consumer)
+ @controller.expects(:logger).returns(mock(:error => true))
+
+ @controller.send(:authenticate_with_open_id, "http://someone.example.com") do |result, identity_url|
+ assert result.missing?
+ assert_equal "Sorry, the OpenID server couldn't be found", result.message
+ end
+ end
+
+ def test_authentication_should_begin_when_the_identity_server_is_present
+ @controller.expects(:begin_open_id_authentication)
+ @controller.send(:authenticate_with_open_id, "http://someone.example.com")
+ end
+end
View
14 test/libs/open_id_authentication/test/status_test.rb
@@ -0,0 +1,14 @@
+require File.dirname(__FILE__) + '/test_helper'
+
+class StatusTest < Test::Unit::TestCase
+ include OpenIdAuthentication
+
+ def test_state_conditional
+ assert Result[:missing].missing?
+ assert Result[:missing].unsuccessful?
+ assert !Result[:missing].successful?
+
+ assert Result[:successful].successful?
+ assert !Result[:successful].unsuccessful?
+ end
+end
View
17 test/libs/open_id_authentication/test/test_helper.rb
@@ -0,0 +1,17 @@
+require 'test/unit'
+require 'rubygems'
+
+gem 'activesupport'
+require 'active_support'
+
+gem 'actionpack'
+require 'action_controller'
+
+gem 'mocha'
+require 'mocha'
+
+gem 'ruby-openid'
+require 'openid'
+
+RAILS_ROOT = File.dirname(__FILE__) unless defined? RAILS_ROOT
+require File.dirname(__FILE__) + "/../lib/open_id_authentication"
View
41 test/libs/rails_trickery.rb
@@ -0,0 +1,41 @@
+# The only reason I am doing all of this non sense is becuase the openid_authentication requires that
+# these constants be present. The only other alternative is to use an entire rails application for testing
+# which is a little too overboard for this, I think.
+
+RAILS_ROOT = ''
+
+class ActionController < Authlogic::TestCase::MockController
+ class Request < Authlogic::TestCase::MockRequest
+ def request_method
+ ""
+ end
+ end
+
+ def root_url
+ ''
+ end
+
+ def request
+ return @request if defined?(@request)
+ super
+ # Rails does some crazy s#!t with the "method" method. If I don't do this I get a "wrong arguments (0 for 1) error"
+ @request.class.class_eval do
+ def method
+ nil
+ end
+ end
+ @request
+ end
+
+ def url_for(*args)
+ ''
+ end
+
+ def redirecting_to
+ @redirect_to
+ end
+
+ def redirect_to(*args)
+ @redirect_to = args
+ end
+end
View
3  test/libs/user.rb
@@ -0,0 +1,3 @@
+class User < ActiveRecord::Base
+ acts_as_authentic
+end
View
2  test/libs/user_session.rb
@@ -0,0 +1,2 @@
+class UserSession < Authlogic::Session::Base
+end
View
32 test/session_test.rb
@@ -0,0 +1,32 @@
+require File.dirname(__FILE__) + '/test_helper.rb'
+
+class SessionTest < ActiveSupport::TestCase
+ def test_openid_identifier
+ session = UserSession.new
+ assert session.respond_to?(:openid_identifier)
+ session.openid_identifier = "test"
+ assert_equal "http://test/", session.openid_identifier
+ end
+
+ def test_validate_openid_error
+ session = UserSession.new
+ session.openid_identifier = "yes"
+ session.openid_identifier = "%"
+ assert_nil session.openid_identifier
+ assert !session.save
+ assert session.errors.on(:openid_identifier)
+ end
+
+ def test_validate_by_nil_openid_identifier
+ session = UserSession.new
+ assert !session.save
+ assert !redirecting_to_yahoo?
+ end
+
+ def test_validate_by_correct_openid_identifier
+ session = UserSession.new
+ session.openid_identifier = "https://me.yahoo.com/a/9W0FJjRj0o981TMSs0vqVxPdmMUVOQ--"
+ assert !session.save
+ assert redirecting_to_yahoo?
+ end
+end
View
78 test/test_helper.rb
@@ -0,0 +1,78 @@
+require "test/unit"
+require "rubygems"
+require "ruby-debug"
+require "active_record"
+
+ActiveRecord::Schema.verbose = false
+ActiveRecord::Base.establish_connection(:adapter => "sqlite3", :dbfile => ":memory:")
+ActiveRecord::Base.configurations = true
+ActiveRecord::Schema.define(:version => 1) do
+ create_table :open_id_authentication_associations, :force => true do |t|
+ t.integer :issued, :lifetime
+ t.string :handle, :assoc_type
+ t.binary :server_url, :secret
+ end
+
+ create_table :open_id_authentication_nonces, :force => true do |t|
+ t.integer :timestamp, :null => false
+ t.string :server_url, :null => true
+ t.string :salt, :null => false
+ end
+
+ create_table :users do |t|
+ t.datetime :created_at
+ t.datetime :updated_at
+ t.integer :lock_version, :default => 0
+ t.string :login
+ t.string :crypted_password
+ t.string :password_salt
+ t.string :persistence_token
+ t.string :single_access_token
+ t.string :perishable_token
+ t.string :openid_identifier
+ t.string :email
+ t.string :first_name
+ t.string :last_name
+ t.integer :login_count, :default => 0, :null => false
+ t.integer :failed_login_count, :default => 0, :null => false
+ t.datetime :last_request_at
+ t.datetime :current_login_at
+ t.datetime :last_login_at
+ t.string :current_login_ip
+ t.string :last_login_ip
+ end
+end
+
+require "active_record/fixtures"
+require "openid"
+require File.dirname(__FILE__) + "/../../authlogic/lib/authlogic"
+require File.dirname(__FILE__) + "/../../authlogic/lib/authlogic/test_case"
+require File.dirname(__FILE__) + "/libs/rails_trickery"
+require File.dirname(__FILE__) + '/libs/open_id_authentication/lib/open_id_authentication'
+ActionController.send(:include, OpenIdAuthentication)
+require File.dirname(__FILE__) + '/../lib/authlogic_openid' unless defined?(AuthlogicOpenid)
+require File.dirname(__FILE__) + '/libs/user'
+require File.dirname(__FILE__) + '/libs/user_session'
+
+class ActiveSupport::TestCase
+ include ActiveRecord::TestFixtures
+ self.fixture_path = File.dirname(__FILE__) + "/fixtures"
+ self.use_transactional_fixtures = false
+ self.use_instantiated_fixtures = false
+ self.pre_loaded_fixtures = false
+ fixtures :all
+ setup :activate_authlogic
+
+ private
+ def activate_authlogic
+ Authlogic::Session::Base.controller = controller
+ end
+
+ def controller
+ @controller ||= Authlogic::TestCase::ControllerAdapter.new(ActionController.new)
+ end
+
+ def redirecting_to_yahoo?
+ controller.redirecting_to.to_s =~ /^https:\/\/open.login.yahooapis.com\/openid\/op\/auth/
+ end
+end
Please sign in to comment.
Something went wrong with that request. Please try again.