Add the open_id_authentication plugin
authorKai Krueger <kakrueger@gmail.com>
Sun, 10 Jan 2010 17:34:00 +0000 (17:34 +0000)
committerKai Krueger <kakrueger@gmail.com>
Sun, 10 Jan 2010 17:34:00 +0000 (17:34 +0000)
The plugin is taken from http://github.com/rails/open_id_authentication
commit 079b91f70602814c98d4345e198f743bb56b76b5

This plugin provides some convenience wrapper functions around
the ruby gem ruby-openid

19 files changed:
vendor/plugins/open_id_authentication/CHANGELOG [new file with mode: 0644]
vendor/plugins/open_id_authentication/README [new file with mode: 0644]
vendor/plugins/open_id_authentication/Rakefile [new file with mode: 0644]
vendor/plugins/open_id_authentication/generators/open_id_authentication_tables/open_id_authentication_tables_generator.rb [new file with mode: 0644]
vendor/plugins/open_id_authentication/generators/open_id_authentication_tables/templates/migration.rb [new file with mode: 0644]
vendor/plugins/open_id_authentication/generators/upgrade_open_id_authentication_tables/templates/migration.rb [new file with mode: 0644]
vendor/plugins/open_id_authentication/generators/upgrade_open_id_authentication_tables/upgrade_open_id_authentication_tables_generator.rb [new file with mode: 0644]
vendor/plugins/open_id_authentication/init.rb [new file with mode: 0644]
vendor/plugins/open_id_authentication/lib/open_id_authentication.rb [new file with mode: 0644]
vendor/plugins/open_id_authentication/lib/open_id_authentication/association.rb [new file with mode: 0644]
vendor/plugins/open_id_authentication/lib/open_id_authentication/db_store.rb [new file with mode: 0644]
vendor/plugins/open_id_authentication/lib/open_id_authentication/nonce.rb [new file with mode: 0644]
vendor/plugins/open_id_authentication/lib/open_id_authentication/request.rb [new file with mode: 0644]
vendor/plugins/open_id_authentication/lib/open_id_authentication/timeout_fixes.rb [new file with mode: 0644]
vendor/plugins/open_id_authentication/tasks/open_id_authentication_tasks.rake [new file with mode: 0644]
vendor/plugins/open_id_authentication/test/normalize_test.rb [new file with mode: 0644]
vendor/plugins/open_id_authentication/test/open_id_authentication_test.rb [new file with mode: 0644]
vendor/plugins/open_id_authentication/test/status_test.rb [new file with mode: 0644]
vendor/plugins/open_id_authentication/test/test_helper.rb [new file with mode: 0644]

diff --git a/vendor/plugins/open_id_authentication/CHANGELOG b/vendor/plugins/open_id_authentication/CHANGELOG
new file mode 100644 (file)
index 0000000..7349bd3
--- /dev/null
@@ -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]
\ No newline at end of file
diff --git a/vendor/plugins/open_id_authentication/README b/vendor/plugins/open_id_authentication/README
new file mode 100644 (file)
index 0000000..807cdc7
--- /dev/null
@@ -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
\ No newline at end of file
diff --git a/vendor/plugins/open_id_authentication/Rakefile b/vendor/plugins/open_id_authentication/Rakefile
new file mode 100644 (file)
index 0000000..31074b8
--- /dev/null
@@ -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
diff --git a/vendor/plugins/open_id_authentication/generators/open_id_authentication_tables/open_id_authentication_tables_generator.rb b/vendor/plugins/open_id_authentication/generators/open_id_authentication_tables/open_id_authentication_tables_generator.rb
new file mode 100644 (file)
index 0000000..6f78afc
--- /dev/null
@@ -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
diff --git a/vendor/plugins/open_id_authentication/generators/open_id_authentication_tables/templates/migration.rb b/vendor/plugins/open_id_authentication/generators/open_id_authentication_tables/templates/migration.rb
new file mode 100644 (file)
index 0000000..ef2a0cf
--- /dev/null
@@ -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
diff --git a/vendor/plugins/open_id_authentication/generators/upgrade_open_id_authentication_tables/templates/migration.rb b/vendor/plugins/open_id_authentication/generators/upgrade_open_id_authentication_tables/templates/migration.rb
new file mode 100644 (file)
index 0000000..d13bbab
--- /dev/null
@@ -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
diff --git a/vendor/plugins/open_id_authentication/generators/upgrade_open_id_authentication_tables/upgrade_open_id_authentication_tables_generator.rb b/vendor/plugins/open_id_authentication/generators/upgrade_open_id_authentication_tables/upgrade_open_id_authentication_tables_generator.rb
new file mode 100644 (file)
index 0000000..02fddd7
--- /dev/null
@@ -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
diff --git a/vendor/plugins/open_id_authentication/init.rb b/vendor/plugins/open_id_authentication/init.rb
new file mode 100644 (file)
index 0000000..808c7bd
--- /dev/null
@@ -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
diff --git a/vendor/plugins/open_id_authentication/lib/open_id_authentication.rb b/vendor/plugins/open_id_authentication/lib/open_id_authentication.rb
new file mode 100644 (file)
index 0000000..b485c5f
--- /dev/null
@@ -0,0 +1,240 @@
+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/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 :file
+      OpenID::Store::Filesystem.new(OPEN_ID_AUTHENTICATION_DIR)
+    else
+      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
diff --git a/vendor/plugins/open_id_authentication/lib/open_id_authentication/association.rb b/vendor/plugins/open_id_authentication/lib/open_id_authentication/association.rb
new file mode 100644 (file)
index 0000000..9654eae
--- /dev/null
@@ -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
diff --git a/vendor/plugins/open_id_authentication/lib/open_id_authentication/db_store.rb b/vendor/plugins/open_id_authentication/lib/open_id_authentication/db_store.rb
new file mode 100644 (file)
index 0000000..780fb6a
--- /dev/null
@@ -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
diff --git a/vendor/plugins/open_id_authentication/lib/open_id_authentication/nonce.rb b/vendor/plugins/open_id_authentication/lib/open_id_authentication/nonce.rb
new file mode 100644 (file)
index 0000000..c52f6c5
--- /dev/null
@@ -0,0 +1,5 @@
+module OpenIdAuthentication
+  class Nonce < ActiveRecord::Base
+    set_table_name :open_id_authentication_nonces
+  end
+end
diff --git a/vendor/plugins/open_id_authentication/lib/open_id_authentication/request.rb b/vendor/plugins/open_id_authentication/lib/open_id_authentication/request.rb
new file mode 100644 (file)
index 0000000..e0cc8e3
--- /dev/null
@@ -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
diff --git a/vendor/plugins/open_id_authentication/lib/open_id_authentication/timeout_fixes.rb b/vendor/plugins/open_id_authentication/lib/open_id_authentication/timeout_fixes.rb
new file mode 100644 (file)
index 0000000..cc711c9
--- /dev/null
@@ -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
\ No newline at end of file
diff --git a/vendor/plugins/open_id_authentication/tasks/open_id_authentication_tasks.rake b/vendor/plugins/open_id_authentication/tasks/open_id_authentication_tasks.rake
new file mode 100644 (file)
index 0000000..c71434a
--- /dev/null
@@ -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
diff --git a/vendor/plugins/open_id_authentication/test/normalize_test.rb b/vendor/plugins/open_id_authentication/test/normalize_test.rb
new file mode 100644 (file)
index 0000000..635d3ab
--- /dev/null
@@ -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
diff --git a/vendor/plugins/open_id_authentication/test/open_id_authentication_test.rb b/vendor/plugins/open_id_authentication/test/open_id_authentication_test.rb
new file mode 100644 (file)
index 0000000..ddcc17b
--- /dev/null
@@ -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
diff --git a/vendor/plugins/open_id_authentication/test/status_test.rb b/vendor/plugins/open_id_authentication/test/status_test.rb
new file mode 100644 (file)
index 0000000..b1d5e09
--- /dev/null
@@ -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
\ No newline at end of file
diff --git a/vendor/plugins/open_id_authentication/test/test_helper.rb b/vendor/plugins/open_id_authentication/test/test_helper.rb
new file mode 100644 (file)
index 0000000..43216e1
--- /dev/null
@@ -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"