]> git.openstreetmap.org Git - rails.git/commitdiff
Merge branch 'master' into openID
authorÆvar Arnfjörð Bjarmason <avarab@gmail.com>
Sat, 24 Apr 2010 12:33:29 +0000 (12:33 +0000)
committerÆvar Arnfjörð Bjarmason <avarab@gmail.com>
Sat, 24 Apr 2010 12:33:29 +0000 (12:33 +0000)
27 files changed:
app/controllers/user_controller.rb
app/models/user.rb
app/views/user/account.html.erb
app/views/user/login.html.erb
app/views/user/new.html.erb
config/environment.rb
config/locales/en.yml
db/migrate/050_add_open_id_authentication_tables.rb [new file with mode: 0644]
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]

index 9551ac6d8fbdd1e21524a6d1f7065f079c7618b9..60e35016a45e37e6b55f5c41b208c34201230c42 100644 (file)
@@ -24,6 +24,18 @@ class UserController < ApplicationController
     if Acl.find_by_address(request.remote_ip, :conditions => {:k => "no_account_creation"})
       render :action => 'new'
     else
+      #The redirect from the OpenID provider reenters here again 
+      #and we need to pass the parameters through to the  
+      #open_id_authentication function a second time 
+      if params[:open_id_complete] 
+        openid_verify('', true) 
+        #We have set the user.openid_url to nil beforehand. If it hasn't 
+        #been set to a new valid openid_url, it means the openid couldn't be validated 
+        if @user.nil? or @user.openid_url.nil? 
+          render :action => 'new' 
+          return 
+        end   
+      else
       @user = User.new(params[:user])
 
       @user.visible = true
@@ -31,6 +43,52 @@ class UserController < ApplicationController
       @user.description = "" if @user.description.nil?
       @user.creation_ip = request.remote_ip
       @user.languages = request.user_preferred_languages
+        #Set the openid_url to nil as for one it is used 
+        #to check if the openid could be validated and secondly 
+        #to not get dupplicate conflicts for an empty openid  
+        @user.openid_url = nil
+
+        if (!params[:user][:openid_url].nil? and params[:user][:openid_url].length > 0)
+          if @user.pass_crypt.length == 0 
+            #if the password is empty, but we have a openid 
+            #then generate a random passowrd to disable 
+            #loging in via password 
+            @user.pass_crypt = ActiveSupport::SecureRandom.base64(16) 
+            @user.pass_crypt_confirmation = @user.pass_crypt 
+          end
+          #Validate all of the other fields before
+          #redirecting to the openid provider
+          if !@user.valid?
+            render :action => 'new'
+          else        
+            #TODO: Is it a problem to store the user variable with respect to password safty in the session variables?
+            #Store the user variable in the session for it to be accessible when redirecting back from the openid provider
+            session[:new_usr] = @user
+            begin
+              @norm_openid_url = OpenIdAuthentication.normalize_identifier(params[:user][:openid_url])
+            rescue
+              flash.now[:error] = t 'user.login.openid invalid'
+              render :action => 'new'
+              return
+            end
+            #Verify that the openid provided is valid and that the user is the owner of the id
+            openid_verify(@norm_openid_url, true)
+            #openid_verify can return in two ways:
+            #Either it returns with a redirect to the openid provider who then freshly
+            #redirects back to this url if the openid is valid, or if the openid is not plausible
+            #and no provider for it could be found it just returns
+            #we want to just let the redirect through
+            if response.headers["Location"].nil?
+              render :action => 'new'
+            end
+          end
+          #At this point there was either an error and the page has been rendered,
+          #or there is a redirect to the openid provider and the rest of the method
+          #gets executed whenn this method gets reentered after redirecting back
+          #from the openid provider
+          return
+        end
+      end
 
       if @user.save
         flash[:notice] = t 'user.new.flash create success message'
@@ -46,6 +104,15 @@ class UserController < ApplicationController
     @title = t 'user.account.title'
     @tokens = @user.oauth_tokens.find :all, :conditions => 'oauth_tokens.invalidated_at is null and oauth_tokens.authorized_at is not null'
 
+    #The redirect from the OpenID provider reenters here again
+    #and we need to pass the parameters through to the 
+    #open_id_authentication function
+    if params[:open_id_complete]
+      openid_verify('', false)
+      @user.save
+      return
+    end
+
     if params[:user] and params[:user][:display_name] and params[:user][:description]
       @user.display_name = params[:user][:display_name]
       @user.new_email = params[:user][:new_email]
@@ -81,6 +148,21 @@ class UserController < ApplicationController
           end
         end
       end
+
+      if (params[:user][:openid_url].length > 0)
+        begin
+          @norm_openid_url = OpenIdAuthentication.normalize_identifier(params[:user][:openid_url])
+          if (@norm_openid_url != @user.openid_url)
+            #If the OpenID has changed, we want to check that it is a valid OpenID and one
+            #the user has control over before saving the openID as a password equivalent for
+            #the user.
+            openid_verify(@norm_openid_url, false)
+          end
+        rescue
+          flash.now[:error] = t 'user.login.openid invalid'
+        end
+      end
+
     else
       if flash[:errors]
         flash[:errors].each do |attr,msg|
@@ -91,6 +173,91 @@ class UserController < ApplicationController
     end
   end
 
+  def openid_specialcase_mapping(openid_url)
+    #Special case gmail.com, as it is pontentially a popular OpenID provider and unlike
+    #yahoo.com, where it works automatically, Google have hidden their OpenID endpoint
+    #somewhere obscure making it less userfriendly.
+    if (openid_url.match(/(.*)gmail.com(\/?)$/) or openid_url.match(/(.*)googlemail.com(\/?)$/) )
+      return 'https://www.google.com/accounts/o8/id'
+    end
+
+    return nil
+  end  
+
+  def openid_verify(openid_url,account_create)
+    authenticate_with_open_id(openid_url) do |result, identity_url|
+      if result.successful?
+        #We need to use the openid url passed back from the OpenID provider
+        #rather than the one supplied by the user, as these can be different.
+        #e.g. one can simply enter yahoo.com in the login box, i.e. no user specific url
+        #only once it comes back from the OpenID provider do we know the unique address for
+        #the user.
+        @user = session[:new_usr] unless @user #this is used for account creation when the user is not yet in the database
+        @user.openid_url = identity_url
+      elsif result.missing?
+        mapped_id = openid_specialcase_mapping(openid_url)
+        if mapped_id
+          openid_verify(mapped_id, account_create)
+        else
+          flash.now[:error] = t 'user.login.openid missing provider'
+        end
+      elsif result.invalid?
+        flash.now[:error] = t 'user.login.openid invalid'
+      else
+        flash.now[:error] = t 'user.login.auth failure'
+      end
+    end
+  end
+
+  def open_id_authentication(openid_url)
+    #TODO: only ask for nickname and email, if we don't already have a user for that openID, in which case
+    #email and nickname are already filled out. I don't know how to do that with ruby syntax though, as we
+    #don't want to duplicate the do block
+    #On the other hand it also doesn't matter too much if we ask every time, as the OpenID provider should
+    #remember these results, and shouldn't repromt the user for these data each time.
+    user = nil
+    authenticate_with_open_id(openid_url, :return_to => request.protocol + request.host_with_port + '/login?referer=' + params[:referer], :optional => [:nickname, :email]) do |result, identity_url, registration|
+      if result.successful?
+        #We need to use the openid url passed back from the OpenID provider
+        #rather than the one supplied by the user, as these can be different.
+        #e.g. one can simply enter yahoo.com in the login box, i.e. no user specific url
+        #only once it comes back from the OpenID provider do we know the unique address for
+        #the user.
+        user = User.find_by_openid_url(identity_url)
+        if user
+          if user.visible? and user.active?
+            session[:user] = user.id
+            session_expires_after 1.month if session[:remember]
+            return user
+          else
+            user = nil
+            flash.now[:error] = t 'user.login.account not active'
+          end
+        else
+          #We don't have a user registered to this OpenID. Redirect to the create account page
+          #with username and email filled in if they have been given by the OpenID provider through
+          #the simple registration protocol
+          redirect_to :controller => 'user', :action => 'new', :nickname => registration['nickname'], :email => registration['email'], :openid => identity_url
+        end
+      else if result.missing?
+             #Try and apply some heuristics to make common cases more userfriendly
+             mapped_id = openid_specialcase_mapping(openid_url)
+             if mapped_id
+               open_id_authentication(mapped_id)
+             else
+               flash.now[:error] = t 'user.login.openid missing provider'
+             end
+           else if result.invalid?
+                  flash.now[:error] = t 'user.login.openid invalid'
+                else
+                  flash.now[:error] = t 'user.login.auth failure'
+                end
+           end
+      end
+    end
+    return user
+  end
+
   def go_public
     @user.data_public = true
     @user.save
@@ -149,34 +316,52 @@ class UserController < ApplicationController
     # The user is logged in already, so don't show them the signup
     # page, instead send them to the home page
     redirect_to :controller => 'site', :action => 'index' if session[:user]
+
+    @nickname = params['nickname']
+    @email = params['email']
+    @openID = params['openid']
   end
 
   def login
     @title = t 'user.login.title'
 
-    if params[:user]
-      email_or_display_name = params[:user][:email]
-      pass = params[:user][:password]
-      user = User.authenticate(:username => email_or_display_name, :password => pass)
-
-      if user
-        session[:user] = user.id
-        session_expires_after 1.month if params[:remember_me]
-
-        # The user is logged in, if the referer param exists, redirect
-        # them to that unless they've also got a block on them, in
-        # which case redirect them to the block so they can clear it.
-        if user.blocked_on_view
-          redirect_to user.blocked_on_view, :referrer => params[:referrer]
-        elsif params[:referer]
-          redirect_to params[:referer]
+    #The redirect from the OpenID provider reenters here again
+    #and we need to pass the parameters through to the 
+    # open_id_authentication function
+    if params[:open_id_complete]
+      user = open_id_authentication('')
+    elsif params[:user]
+      if !params[:user][:openid_url].nil? and !params[:user][:openid_url].empty?
+        session[:remember] = params[:remember_me]
+        #construct the openid request. This will redirect to the OpenID server to ask for validation
+        #The external OpenID server will then redirect back to the login method and reenters at the top
+        open_id_authentication(params[:user][:openid_url])
+        return
+      else
+        email_or_display_name = params[:user][:email]
+        pass = params[:user][:password]
+
+        if user = User.authenticate(:username => email_or_display_name, :password => pass)
+          session[:user] = user.id
+          session_expires_after 1.month if params[:remember_me]
+        elsif User.authenticate(:username => email_or_display_name, :password => pass, :inactive => true)
+          flash.now[:error] = t 'user.login.account not active'
         else
-          redirect_to :controller => 'site', :action => 'index'
+          flash.now[:error] = t 'user.login.auth failure'
         end
-       elsif User.authenticate(:username => email_or_display_name, :password => pass, :inactive => true)
-        flash.now[:error] = t 'user.login.account not active'
+      end
+    end
+
+    if user
+      # The user is logged in, if the referer param exists, redirect
+      # them to that unless they've also got a block on them, in
+      # which case redirect them to the block so they can clear it.
+      if user.blocked_on_view
+        redirect_to user.blocked_on_view, :referrer => params[:referrer]
+      elsif params[:referer]
+        redirect_to params[:referer]
       else
-        flash.now[:error] = t 'user.login.auth failure'
+        redirect_to :controller => 'site', :action => 'index'
       end
     end
   end
index f02c9a5cd0067b63afc3984575534c38fb0a550b..09e1a7d35611a9c3178a4832a3b19845b5faf0fe 100644 (file)
@@ -22,6 +22,7 @@ class User < ActiveRecord::Base
   validates_confirmation_of :pass_crypt#, :message => ' must match the confirmation password'
   validates_uniqueness_of :display_name, :allow_nil => true
   validates_uniqueness_of :email
+  validates_uniqueness_of :openid_url, :allow_nil => true
   validates_length_of :pass_crypt, :within => 8..255
   validates_length_of :display_name, :within => 3..255, :allow_nil => true
   validates_email_format_of :email
index 85e9aebefb300142b3465176b81ee0f3b33c88ad..e1fe719e0795768c4ed8d421d5085c2ad8f6c81b 100644 (file)
     <td class="fieldName"><%= t 'user.new.confirm password' %></td>
     <td><%= f.password_field :pass_crypt_confirmation, {:value => '', :size => 30, :maxlength => 255, :autocomplete => :off} %></td>
   </tr>
+  <tr>
+    <td class="fieldName" ><%= t 'user.account.openid.openid' %></td>
+    <td style="padding-bottom:0px;"><%= f.text_field :openid_url %> (<a href="<%= t 'user.account.openid.link' %>" target="_new"><%= t 'user.account.openid.link text' %></a>) </td>
+  </tr>
 
   <tr>
     <td class="fieldName" valign="top"><%= t 'user.account.public editing.heading' %></td>
index cf7f4a8198097a2753e4bc4d20f46cc2de4f6d85..e394a8c27f31ca377e26f18b468b7fc9251be8c4 100644 (file)
@@ -6,9 +6,11 @@
 <%= hidden_field_tag('referer', h(params[:referer])) %>
 <table id="loginForm">
   <tr><td class="fieldName"><%= t 'user.login.email or username' %></td><td><%= text_field('user', 'email',{:value => "", :size => 28, :maxlength => 255, :tabindex => 1}) %></td></tr>
-  <tr><td class="fieldName"><%= t 'user.login.password' %></td><td><%= password_field('user', 'password',{:value => "", :size => 28, :maxlength => 255, :tabindex => 2}) %> <span class="minorNote">(<%= link_to t('user.login.lost password link'), :controller => 'user', :action => 'lost_password' %>)</span></td></tr>
-  <tr><td class="fieldName"><label for="remember_me"><%= t 'user.login.remember' %></label></td><td><%= check_box_tag "remember_me", "yes", false, :tabindex => 3 %></td></tr> 
-  <tr><td colspan="2">&nbsp;<!--vertical spacer--></td></tr>
-  <tr><td></td><td align="right"><%= submit_tag t('user.login.login_button'), :tabindex => 3 %></td></tr>
+  <tr><td class="fieldName"><%= t 'user.login.password' %></td><td><%= password_field('user', 'password',{:value => "", :size => 28, :maxlength => 255, :tabindex => 2}) %></td><td> <span class="minorNote">(<%= link_to t('user.login.lost password link'), :controller => 'user', :action => 'lost_password' %>)</span></td></tr>
+  <tr><td colspan = "3"><h4><I><%= t 'user.login.alternatively' %></I></h4></td></tr>
+  <tr><td class="fieldName"><%= t 'user.login.openid' %></td><td><%= text_field('user', 'openid_url',{:size => 28, :maxlength => 255, :tabindex => 3}) %></td><td> <span class="minorNote">(<a href="<%= t 'user.account.openid.link' %>" target="_new"><%= t 'user.account.openid.link text' %></a>)</span></td></tr>
+  <tr><td colspan="3">&nbsp;<!--vertical spacer--></td></tr>
+  <tr><td colspan="3">&nbsp;<!--vertical spacer--></td></tr>
+  <tr><td class="fieldName"><label for="remember_me"><%= t 'user.login.remember' %></label></td><td><%= check_box_tag "remember_me", "yes", false, :tabindex => 3 %></td><td align=right><%= submit_tag t('user.login.login_button'), :tabindex => 3 %></td></tr>
 </table>
 <% end %>
index bd85664c04d7c10b00e3245d648c91f74fc93af3..e7aa9313d0cc99e0e550e99dd82a04189ee316a3 100644 (file)
 <% form_tag :action => 'save' do %>
 <%= hidden_field_tag('referer', h(params[:referer])) unless params[:referer].nil? %>
 <table id="signupForm">
-  <tr><td class="fieldName"><%= t 'user.new.email address' %></td><td><%= text_field('user', 'email',{:size => 50, :maxlength => 255, :tabindex => 1}) %></td></tr>
+  <tr><td class="fieldName"><%= t 'user.new.email address' %></td><td><%= text_field('user', 'email',{:size => 50, :maxlength => 255, :tabindex => 1, :value => @email}) %></td></tr>
   <tr><td class="fieldName"><%= t 'user.new.confirm email address' %></td><td><%= text_field('user', 'email_confirmation',{:size => 50, :maxlength => 255, :tabindex => 2}) %></td></tr>
   <tr><td></td><td><span class="minorNote"><%= t 'user.new.not displayed publicly' %></span></td></tr>
   <tr><td colspan="2">&nbsp;<!--vertical spacer--></td></tr>
-  <tr><td class="fieldName"><%= t 'user.new.display name' %></td><td><%= text_field('user', 'display_name',{:size => 30, :maxlength => 255, :tabindex => 3}) %></td></tr>
+  <tr><td class="fieldName"><%= t 'user.new.display name' %></td><td><%= text_field('user', 'display_name',{:size => 30, :maxlength => 255, :tabindex => 3, :value => @nickname}) %></td></tr>
   <tr><td></td><td><span class="minorNote"><%= t 'user.new.display name description' %></span></td></tr>
   <tr><td colspan="2">&nbsp;<!--vertical spacer--></td></tr>
   <tr><td class="fieldName"><%= t 'user.new.password' %></td><td><%= password_field('user', 'pass_crypt',{:size => 30, :maxlength => 255, :tabindex => 4}) %></td></tr>
   <tr><td class="fieldName"><%= t 'user.new.confirm password' %></td><td><%= password_field('user', 'pass_crypt_confirmation',{:size => 30, :maxlength => 255, :tabindex => 5}) %></td></tr>
   
   <tr><td colspan="2">&nbsp;<!--vertical spacer--></td></tr>
-  <tr><td></td><td align="right"><input type="submit" value="<%= t'user.new.signup' %>" tabindex="6"></td></tr>
+  <tr><td class="fieldName"><%= t 'user.new.openID' %></td><td><%= text_field('user', 'openid_url',{:size => 50, :maxlength => 255, :tabindex => 6, :value => @openID}) %></td></tr>
+  <tr><td></td><td><span class="minorNote"><%= t 'user.new.openID description' %></span></td></tr>
+  <tr><td colspan="2">&nbsp;<!--vertical spacer--></td></tr>
+  <tr><td></td><td align="right"><input type="submit" value="<%= t'user.new.signup' %>" tabindex="7"></td></tr>
 </table>
 <% end %>
 
index 539af83b2971842a09296fe6456493c4c79e3571..01115ba97fb17133e9e47eb38cb829faac36ba50 100644 (file)
@@ -52,6 +52,7 @@ Rails::Initializer.run do |config|
   config.gem 'rmagick', :lib => 'RMagick'
   config.gem 'oauth', :version => '>= 0.3.6'
   config.gem 'httpclient'
+  config.gem 'ruby-openid', :lib => 'openid', :version => '>=2.0.4'
   config.gem 'SystemTimer', :version => '>= 1.1.3', :lib => 'system_timer'
   config.gem 'sanitize'
 
index f1e0e6a8581c1cc098c987c0f802d026a4d3cea4..02eff8f5608fbc8f2f2475aa621dff9c07594596 100644 (file)
@@ -1373,11 +1373,16 @@ en:
       create_account: "create an account"
       email or username: "Email Address or Username:"
       password: "Password:"
+      openid: "OpenID:"
+      openid description: "Use your OpenID to login"
+      alternatively: "Alternatively"
       remember: "Remember me:"
       lost password link: "Lost your password?"
       login_button: "Login"
       account not active: "Sorry, your account is not active yet.<br />Please click on the link in the account confirmation email to activate your account."
       auth failure: "Sorry, could not log in with those details."
+      openid missing provider: "Sorry, could not contact your OpenID provider"
+      openid invalid: "Sorry, your OpenID seems misformed"
     logout:
       title: "Logout"
       heading: "Logout from OpenStreetMap"
@@ -1412,6 +1417,8 @@ en:
       display name description: "Your publicly displayed username. You can change this later in the preferences."
       password: "Password:"
       confirm password: "Confirm Password:"
+      openID: "OpenID:"
+      openID description: '(Optional) If you have an <a href="http://wiki.openstreetmap.org/wiki/openID">OpenID</a> you can associate it with this account to login'
       signup: Signup
       flash create success message: "User was successfully created. Check your email for a confirmation note, and you will be mapping in no time :-)<br /><br />Please note that you will not be able to login until you've received and confirmed your email address.<br /><br />If you use an antispam system which sends confirmation requests then please make sure you whitelist webmaster@openstreetmap.org as we are unable to reply to any confirmation requests."
     no_such_user:
@@ -1475,6 +1482,10 @@ en:
       current email address: "Current Email Address:"
       new email address: "New Email Address:"
       email never displayed publicly: "(never displayed publicly)"
+      openid:
+        openid: "OpenID:"
+        link: "http://wiki.openstreetmap.org/wiki/OpenID"
+        link text: "what is this?"
       public editing:
         heading: "Public editing:"
         enabled: "Enabled. Not anonymous and can edit data."
diff --git a/db/migrate/050_add_open_id_authentication_tables.rb b/db/migrate/050_add_open_id_authentication_tables.rb
new file mode 100644 (file)
index 0000000..7dfff20
--- /dev/null
@@ -0,0 +1,30 @@
+class AddOpenIdAuthenticationTables < 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
+    
+    add_column :users, :openid_url, :string 
+
+    add_index :users, [:openid_url], :name => "user_openid_unique_idx", :unique => true
+    add_index :open_id_authentication_associations, [:server_url], :name => "open_id_associations_server_url_idx"
+    add_index :open_id_authentication_nonces, [:timestamp], :name => "open_id_nonces_timestamp_idx"
+  end
+
+  def self.down
+    remove_index :users, :name => "user_openid_unique_idx"
+    remove_index :open_id_authentication_associations, :name => "open_id_associations_server_url_idx"
+    remove_index :open_id_authentication_nonces, :name => "open_id_nonces_timestamp_idx"
+    remove_column :users, :openid_url
+    drop_table :open_id_authentication_associations
+    drop_table :open_id_authentication_nonces
+  end
+end
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"