Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Added OpenID support

  • Loading branch information...
commit fcea1ee36849f5548f2ea36971bc71d2118a6942 1 parent 05b9ab8
@binarylogic authored
Showing with 1,130 additions and 21 deletions.
  1. +15 −5 app/controllers/user_sessions_controller.rb
  2. +14 −1 app/models/user.rb
  3. +32 −0 app/models/user_session.rb
  4. +11 −5 app/views/user_sessions/new.html.erb
  5. +3 −0  app/views/users/_form.erb
  6. +1 −1  app/views/users/edit.html.erb
  7. +11 −5 app/views/users/show.html.erb
  8. +3 −3 config/environment.rb
  9. +1 −1  config/routes.rb
  10. +17 −0 db/migrate/20081119233359_add_users_openid_field.rb
  11. +20 −0 db/migrate/20081120163933_create_openid_tables.rb
  12. +35 −0 vendor/plugins/open_id_authentication/CHANGELOG
  13. +218 −0 vendor/plugins/open_id_authentication/README
  14. +22 −0 vendor/plugins/open_id_authentication/Rakefile
  15. +11 −0 .../open_id_authentication/generators/open_id_authentication_tables/open_id_authentication_tables_generator.rb
  16. +20 −0 vendor/plugins/open_id_authentication/generators/open_id_authentication_tables/templates/migration.rb
  17. +26 −0 vendor/plugins/open_id_authentication/generators/upgrade_open_id_authentication_tables/templates/migration.rb
  18. +11 −0 ...ication/generators/upgrade_open_id_authentication_tables/upgrade_open_id_authentication_tables_generator.rb
  19. +17 −0 vendor/plugins/open_id_authentication/init.rb
  20. +176 −0 vendor/plugins/open_id_authentication/lib/open_id_authentication.rb
  21. +9 −0 vendor/plugins/open_id_authentication/lib/open_id_authentication/association.rb
  22. +55 −0 vendor/plugins/open_id_authentication/lib/open_id_authentication/db_store.rb
  23. +73 −0 vendor/plugins/open_id_authentication/lib/open_id_authentication/mem_cache_store.rb
  24. +5 −0 vendor/plugins/open_id_authentication/lib/open_id_authentication/nonce.rb
  25. +17 −0 vendor/plugins/open_id_authentication/lib/open_id_authentication/request.rb
  26. +20 −0 vendor/plugins/open_id_authentication/lib/open_id_authentication/timeout_fixes.rb
  27. +30 −0 vendor/plugins/open_id_authentication/tasks/open_id_authentication_tasks.rake
  28. +151 −0 vendor/plugins/open_id_authentication/test/mem_cache_store_test.rb
  29. +32 −0 vendor/plugins/open_id_authentication/test/normalize_test.rb
  30. +46 −0 vendor/plugins/open_id_authentication/test/open_id_authentication_test.rb
  31. +14 −0 vendor/plugins/open_id_authentication/test/status_test.rb
  32. +14 −0 vendor/plugins/open_id_authentication/test/test_helper.rb
View
20 app/controllers/user_sessions_controller.rb
@@ -8,11 +8,21 @@ def new
def create
@user_session = UserSession.new(params[:user_session])
- if @user_session.save
- flash[:notice] = "Login successful!"
- redirect_back_or_default account_url
- else
- render :action => :new
+ # We are saving with a block to accomodate for OpenID authentication
+ # If you are not using OpenID you can save without a block:
+ #
+ # if @user_session.save
+ # # ... successful login
+ # else
+ # # ... unsuccessful login
+ # end
+ @user_session.save do |result|
+ if result
+ flash[:notice] = "Login successful!"
+ redirect_back_or_default account_url
+ else
+ render :action => :new
+ end
end
end
View
15 app/models/user.rb
@@ -1,8 +1,21 @@
class User < ActiveRecord::Base
- acts_as_authentic
+ acts_as_authentic :allow_blank_login_and_password_fields => true
+
+ validates_presence_of :login, :if => Proc.new { |user| user.openid_identifier.blank? }
+ validate :normalize_openid_identifier
+ validates_uniqueness_of :openid_identifier, :allow_blank => true
def deliver_password_reset_instructions!
reset_perishable_token!
Notifier.deliver_password_reset_instructions(self)
end
+
+ private
+ def normalize_openid_identifier
+ begin
+ self.openid_identifier = OpenIdAuthentication.normalize_url(openid_identifier) if !openid_identifier.blank?
+ rescue OpenIdAuthentication::InvalidOpenId => e
+ errors.add(:openid_identifier, e.message)
+ end
+ end
end
View
32 app/models/user_session.rb
@@ -1,2 +1,34 @@
class UserSession < Authlogic::Session::Base
+ attr_accessor :openid_identifier
+
+ def authenticating_with_openid?
+ !openid_identifier.blank? || controller.params[:open_id_complete]
+ end
+
+ def save(&block)
+ if authenticating_with_openid?
+ raise ArgumentError.new("You must supply a block to authenticate with OpenID") unless block_given?
+
+ controller.send(:authenticate_with_open_id, openid_identifier) do |result, openid_identifier|
+ if !result.successful?
+ errors.add_to_base(result.message)
+ yield false
+ return
+ end
+
+ record = klass.find_by_openid_identifier(openid_identifier)
+
+ if !record
+ errors.add(:openid_identifier, "did not match any users in our database, have you set up your account to use OpenID?")
+ yield false
+ return
+ end
+
+ self.unauthorized_record = record
+ super
+ end
+ else
+ super
+ end
+ end
end
View
16 app/views/user_sessions/new.html.erb
@@ -2,11 +2,17 @@
<% form_for @user_session, :url => user_session_path do |f| %>
<%= f.error_messages %>
- <%= f.label :login %><br />
- <%= f.text_field :login %><br />
- <br />
- <%= f.label :password %><br />
- <%= f.password_field :password %><br />
+ <div id="login_container"<% unless @user_session.openid_identifier.blank? %> style="display: none;"<% end %>>
+ <%= f.label :login %> (or <%= link_to_function "login using OpenID", "$('login_container').toggle(); $('openid_container').toggle();" %>)<br />
+ <%= f.text_field :login %><br />
+ <br />
+ <%= f.label :password %><br />
+ <%= f.password_field :password %><br />
+ </div>
+ <div id="openid_container"<% if @user_session.openid_identifier.blank? %> style="display: none;"<% end %>>
+ <%= f.label :openid_identifier, "OpenID" %> (or <%= link_to_function "login using a standard username / password", "$('login_container').toggle(); $('openid_container').toggle();" %>)<br /><br />
+ <%= f.text_field :openid_identifier %><br />
+ </div>
<br />
<%= f.check_box :remember_me %><%= f.label :remember_me %><br />
<br />
View
3  app/views/users/_form.erb
@@ -7,6 +7,9 @@
<%= form.label :confirm_password%><br />
<%= form.password_field :confirm_password %><br />
<br />
+<%= form.label :openid_identifier, "Or use OpenID instead of a standard login / password" %><br />
+<%= form.text_field :openid_identifier %><br />
+<br />
<%= form.label :email %><br />
<%= form.text_field :email %><br />
<br />
View
2  app/views/users/edit.html.erb
@@ -6,4 +6,4 @@
<%= f.submit "Update" %>
<% end %>
-<%= link_to "My Profile", account_path %>
+<br /><%= link_to "My Profile", account_path %>
View
16 app/views/users/show.html.erb
@@ -1,8 +1,14 @@
-<p>
- <b>Login:</b>
- <%=h @user.login %>
-</p>
-
+<% if @user.openid_identifier.blank? %>
+ <p>
+ <b>Login:</b>
+ <%=h @user.login %>
+ </p>
+<% else %>
+ <p>
+ <b>OpenID:</b>
+ <%=h @user.openid_identifier %>
+ </p>
+<% end %>
<p>
<b>Email:</b>
<%=h @user.email %>
View
6 config/environment.rb
@@ -25,14 +25,14 @@
# config.gem "bj"
# config.gem "hpricot", :version => '0.6', :source => "http://code.whytheluckystiff.net"
# config.gem "aws-s3", :lib => "aws/s3"
- config.gem "authlogic"
+ #config.gem "authlogic"
# Only load the plugins named here, in the order given. By default, all plugins
# in vendor/plugins are loaded in alphabetical order.
# :all can be used as a placeholder for all plugins not explicitly named
# config.plugins = [ :exception_notification, :ssl_requirement, :all ]
- #config.plugin_paths += ["#{RAILS_ROOT}/../../Libs"]
- #config.plugins = [:authlogic]
+ config.plugin_paths += ["#{RAILS_ROOT}/../../Libs"]
+ config.plugins = [:authlogic, :open_id_authentication]
# Add additional load paths for your own custom dirs
# config.load_paths += %W( #{RAILS_ROOT}/extras )
View
2  config/routes.rb
@@ -3,5 +3,5 @@
map.resources :password_resets
map.resources :users
map.resource :user_session
- map.default "/", :controller => "user_sessions", :action => "new"
+ map.root :controller => "user_sessions", :action => "new"
end
View
17 db/migrate/20081119233359_add_users_openid_field.rb
@@ -0,0 +1,17 @@
+class AddUsersOpenidField < ActiveRecord::Migration
+ def self.up
+ add_column :users, :openid_identifier, :string
+ add_index :users, :openid_identifier
+
+ change_column :users, :login, :string, :default => nil, :null => true
+ change_column :users, :crypted_password, :string, :default => nil, :null => true
+ change_column :users, :password_salt, :string, :default => nil, :null => true
+ end
+
+ def self.down
+ remove_column :users, :openid_identifier
+ change_column :users, :login, :string, :default => "", :null => false
+ change_column :users, :crypted_password, :string, :default => "", :null => false
+ change_column :users, :password_salt, :string, :default => "", :null => false
+ end
+end
View
20 db/migrate/20081120163933_create_openid_tables.rb
@@ -0,0 +1,20 @@
+class CreateOpenidTables < 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
35 vendor/plugins/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
218 vendor/plugins/open_id_authentication/README
@@ -0,0 +1,218 @@
+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
+
+
+Copyright (c) 2007 David Heinemeier Hansson, released under the MIT license
View
22 vendor/plugins/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 vendor/plugins/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 ...r/plugins/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
17 vendor/plugins/open_id_authentication/init.rb
@@ -0,0 +1,17 @@
+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
+ ActionController::Base.send :include, OpenIdAuthentication
+end
View
176 vendor/plugins/open_id_authentication/lib/open_id_authentication.rb
@@ -0,0 +1,176 @@
+require 'uri'
+require 'openid/extensions/sreg'
+require 'openid/store/filesystem'
+
+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
+
+ def self.normalize_url(url)
+ uri = URI.parse(url.to_s.strip)
+ uri = URI.parse("http://#{uri}") unless uri.scheme
+ uri.scheme = uri.scheme.downcase # URI should do this
+ uri.normalize.to_s
+ rescue URI::InvalidURIError
+ raise InvalidOpenId.new("#{url} is not an OpenID URL")
+ end
+
+ protected
+ def normalize_url(url)
+ OpenIdAuthentication.normalize_url(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_url(identity_url)
+ return_to = options.delete(:return_to)
+ method = options.delete(:method)
+
+ open_id_request = open_id_consumer.begin(identity_url)
+ add_simple_registration_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_url(open_id_response.display_identifier) if open_id_response.display_identifier
+
+ case open_id_response.status
+ when OpenID::Consumer::SUCCESS
+ yield Result[:successful], identity_url, OpenID::SReg::Response.from_success_response(open_id_response)
+ 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
+ sreg_request.request_fields(Array(fields[:required]).map(&:to_s), true) if fields[:required]
+ sreg_request.request_fields(Array(fields[:optional]).map(&:to_s), false) if fields[:optional]
+ sreg_request.policy_url = fields[:policy_url] if fields[:policy_url]
+ open_id_request.add_extension(sreg_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 + 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 vendor/plugins/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 vendor/plugins/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 vendor/plugins/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 vendor/plugins/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
17 vendor/plugins/open_id_authentication/lib/open_id_authentication/request.rb
@@ -0,0 +1,17 @@
+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
+
+ActionController::AbstractRequest.send :include, OpenIdAuthentication::Request
View
20 vendor/plugins/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 vendor/plugins/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 vendor/plugins/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 vendor/plugins/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/"
+ }
+
+ def test_normalizations
+ NORMALIZATIONS.each do |from, to|
+ assert_equal to, normalize_url(from)
+ end
+ end
+
+ def test_broken_open_id
+ assert_raises(InvalidOpenId) { normalize_url(nil) }
+ assert_raises(InvalidOpenId) { normalize_url("=name") }
+ end
+end
View
46 vendor/plugins/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 vendor/plugins/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
14 vendor/plugins/open_id_authentication/test/test_helper.rb
@@ -0,0 +1,14 @@
+require 'test/unit'
+require 'rubygems'
+
+gem 'activesupport'
+require 'active_support'
+
+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"
Please sign in to comment.
Something went wrong with that request. Please try again.