Merge branch 'master' into openid
authorTom Hughes <tom@compton.nu>
Fri, 14 May 2010 21:50:17 +0000 (22:50 +0100)
committerTom Hughes <tom@compton.nu>
Fri, 14 May 2010 21:50:17 +0000 (22:50 +0100)
Conflicts:
config/locales/is.yml

60 files changed:
app/controllers/user_controller.rb
app/helpers/user_helper.rb
app/models/user.rb
app/views/user/account.html.erb
app/views/user/login.html.erb
app/views/user/new.html.erb
app/views/user/terms.html.erb
config/environment.rb
config/locales/en.yml
config/locales/is.yml
db/migrate/053_add_open_id_authentication_tables.rb [new file with mode: 0644]
public/images/google.png [new file with mode: 0644]
public/images/myopenid.png [new file with mode: 0644]
public/images/myspace.png [new file with mode: 0644]
public/images/openid_input.png [new file with mode: 0644]
public/images/openid_large.png [new file with mode: 0644]
public/images/openid_small.png [new file with mode: 0644]
public/images/wordpress.png [new file with mode: 0644]
public/images/yahoo.png [new file with mode: 0644]
public/stylesheets/common.css
public/stylesheets/large.css
public/stylesheets/small.css
test/fixtures/users.yml
test/integration/client_application_test.rb
test/integration/user_blocks_test.rb
test/integration/user_creation_test.rb
test/integration/user_diaries_test.rb
test/integration/user_login_test.rb [new file with mode: 0644]
test/integration/user_roles_test.rb
test/test_helper.rb
vendor/gems/rots-0.2.1/AUTHORS [new file with mode: 0644]
vendor/gems/rots-0.2.1/README [new file with mode: 0644]
vendor/gems/rots-0.2.1/Rakefile [new file with mode: 0644]
vendor/gems/rots-0.2.1/bin/rots [new file with mode: 0755]
vendor/gems/rots-0.2.1/lib/rots.rb [new file with mode: 0644]
vendor/gems/rots-0.2.1/lib/rots/identity_page_app.rb [new file with mode: 0644]
vendor/gems/rots-0.2.1/lib/rots/server_app.rb [new file with mode: 0644]
vendor/gems/rots-0.2.1/lib/rots/test_helper.rb [new file with mode: 0644]
vendor/gems/rots-0.2.1/rots.gemspec [new file with mode: 0644]
vendor/gems/rots-0.2.1/spec/server_app_spec.rb [new file with mode: 0644]
vendor/gems/rots-0.2.1/spec/spec_helper.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 e07b65af76502278deb69b98c62283bc76f20e39..726fb1cd7b81d075e160e145e14ea0b9fa969482 100644 (file)
@@ -20,8 +20,6 @@ class UserController < ApplicationController
 
   def terms
     @title = t 'user.new.title'
-    @user = User.new(params[:user])
-
     @legale = params[:legale] || OSM.IPToCountry(request.remote_ip) || APP_CONFIG['default_legale']
     @text = OSM.legal_text_for_country(@legale)
 
@@ -29,8 +27,46 @@ class UserController < ApplicationController
       render :update do |page|
         page.replace_html "contributorTerms", :partial => "terms"
       end
-    elsif @user.invalid?
-      render :action => 'new'
+    elsif params[:open_id_complete] 
+      # The redirect from the OpenID provider reenters here
+      # again and we need to pass the parameters through to
+      # the open_id_authentication function
+      @user = session.delete(:new_user)
+
+      openid_verify(nil, @user) do |user|
+      end
+
+      if @user.openid_url.nil? or @user.invalid?
+        render :action => 'new'
+      else
+        render :action => 'terms'
+      end
+    else
+      session[:referer] = params[:referer]
+
+      @user = User.new(params[:user])
+
+      if params[:user][:openid_url] and @user.pass_crypt.empty?
+        # We are creating an account with OpenID and no password
+        # was specified so create a random one
+        @user.pass_crypt = ActiveSupport::SecureRandom.base64(16) 
+        @user.pass_crypt_confirmation = @user.pass_crypt 
+      end
+
+      if @user.valid?
+        if params[:user][:openid_url].nil? or
+            params[:user][:openid_url].empty?
+          # No OpenID so just move on to the terms
+          render :action => 'terms'
+        else
+          # Verify OpenID before moving on
+          session[:new_user] = @user
+          openid_verify(params[:user][:openid_url], @user)
+        end
+      else
+        # Something is wrong, so rerender the form
+        render :action => 'new'
+      end
     end
   end
 
@@ -53,7 +89,7 @@ class UserController < ApplicationController
 
       if @user.save
         flash[:notice] = t 'user.new.flash create success message'
-        Notifier.deliver_signup_confirm(@user, @user.tokens.create(:referer => params[:referer]))
+        Notifier.deliver_signup_confirm(@user, @user.tokens.create(:referer => session.delete(:referer)))
         redirect_to :action => 'login'
       else
         render :action => 'new'
@@ -65,7 +101,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'
 
-    if params[:user] and params[:user][:display_name] and params[:user][:description]
+    if params[:open_id_complete]
+      # The redirect from the OpenID provider reenters here
+      # again and we need to pass the parameters through to
+      # the open_id_authentication function
+      @user = session.delete(:new_user)
+      openid_verify(nil, @user) do |user|
+        update_user(user)
+      end
+    elsif 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]
 
@@ -85,29 +129,17 @@ class UserController < ApplicationController
       @user.home_lat = params[:user][:home_lat]
       @user.home_lon = params[:user][:home_lon]
 
-      if @user.save
-        set_locale
-
-        if @user.new_email.nil? or @user.new_email.empty?
-          flash[:notice] = t 'user.account.flash update success'
-        else
-          flash[:notice] = t 'user.account.flash update success confirm needed'
+      @user.openid_url = nil if params[:user][:openid_url].empty?
 
-          begin
-            Notifier.deliver_email_confirm(@user, @user.tokens.create)
-          rescue
-            # Ignore errors sending email
-          end
-        end
-
-        redirect_to :action => "account", :display_name => @user.display_name
-      end
-    else
-      if flash[:errors]
-        flash[:errors].each do |attr,msg|
-          attr = "new_email" if attr == "email"
-          @user.errors.add(attr,msg)
-        end
+      if params[:user][:openid_url].length > 0 and
+         params[:user][: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
+        # it as a password equivalent for the user.
+        session[:new_user] = @user
+        openid_verify(params[:user][:openid_url], @user)
+      else
+        update_user(@user)
       end
     end
   end
@@ -166,41 +198,29 @@ class UserController < ApplicationController
 
   def new
     @title = t 'user.new.title'
-
-    # 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]
+    @referer = params[:referer] || session[:referer]
+
+    if session[:user]
+      # 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'
+    elsif not params['openid'].nil?
+      flash.now[:notice] = t 'user.new.openid association'
+    end
   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 request.post?
+      session[:remember_me] ||= params[:remember_me]
+      session[:referer] ||= params[:referer]
 
-      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]
-        else
-          redirect_to :controller => 'site', :action => 'index'
-        end
-      elsif User.authenticate(:username => email_or_display_name, :password => pass, :pending => true)
-        flash.now[:error] = t 'user.login.account not active'
-      elsif User.authenticate(:username => email_or_display_name, :password => pass, :suspended => true)
-        flash.now[:error] = t 'user.login.account suspended'
+      if using_open_id?(params[:openid_url])
+        openid_authentication(params[:openid_url])
       else
-        flash.now[:error] = t 'user.login.auth failure'
+        password_authentication(params[:username], params[:password])
       end
+    else
+      @title = t 'user.login.title'
     end
   end
 
@@ -374,6 +394,164 @@ class UserController < ApplicationController
 
 private
 
+  ##
+  # handle password authentication
+  def password_authentication(username, password)
+    if user = User.authenticate(:username => username, :password => password)
+      successful_login(user)
+    elsif User.authenticate(:username => username, :password => password, :pending => true)
+      failed_login t('user.login.account not active')
+    elsif User.authenticate(:username => username, :password => password, :suspended => true)
+      failed_login t('user.login.account suspended')
+    else
+      failed_login t('user.login.auth failure')
+    end
+  end
+
+  ##
+  # handle OpenID authentication
+  def openid_authentication(openid_url)
+    # If we don't appear to have a user for this URL then ask the
+    # provider for some extra information to help with signup
+    if openid_url and User.find_by_openid_url(openid_url)
+      optional = nil
+    else
+      optional = [:nickname, :email]
+    end
+
+    # Start the authentication
+    authenticate_with_open_id(openid_url, :optional => optional) 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.
+        #
+        # For example, you can simply enter yahoo.com in the login box rather
+        # than a user specific url. Only once it comes back from the provider
+        # provider do we know the unique address for the user.
+        if user = User.find_by_openid_url(identity_url)
+          case user.status
+            when "pending" then failed_login t('user.login.account not active')
+            when "active", "confirmed" then successful_login(user)
+            when "suspended" then failed_login t('user.login.account suspended')
+            else failed_login t('user.login.auth failure')
+          end
+        else
+          # We don't have a user registered to this OpenID, so 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
+      elsif result.missing?
+        # Try and apply some heuristics to make common cases more user friendly
+        if openid_url = openid_alternate_url(openid_url)
+          openid_authentication(openid_url)
+        else
+          failed_login t('user.login.openid missing provider')
+        end
+      elsif result.invalid?
+        failed_login t('user.login.openid invalid')
+      else
+        failed_login t('user.login.auth failure')
+      end
+    end
+  end
+
+  ##
+  # verify an OpenID URL
+  def openid_verify(openid_url, user)
+    user.openid_url = openid_url
+
+    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.
+        #
+        # For example, you can simply enter yahoo.com in the login box rather
+        # than a user specific url. Only once it comes back from the provider
+        # provider do we know the unique address for the user.
+        user.openid_url = identity_url
+        yield user
+      elsif result.missing?
+        # Try and apply some heuristics to make common cases more user friendly
+        if openid_url = openid_alternate_url(openid_url)
+          openid_verify(openid_url, user)
+        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
+
+  ##
+  # special case some common OpenID providers by applying heuristics
+  # to try and come up with an alternate URL if the supplied one fails
+  def openid_alternate_url(openid_url)
+    # Special case gmail.com as it is potentially a popular OpenID
+    # provider and, unlike yahoo.com, where it works automatically, Google
+    # have hidden their OpenID endpoint somewhere obscure this making it
+    # somewhat less user friendly.
+    if openid_url.match(/(.*)gmail.com(\/?)$/) or openid_url.match(/(.*)googlemail.com(\/?)$/)
+      return 'https://www.google.com/accounts/o8/id'
+    else
+      return nil
+    end
+  end  
+
+  ##
+  # process a successful login
+  def successful_login(user)
+    session[:user] = user.id
+
+    session_expires_after 1.month if session[:remember_me]
+
+    if user.blocked_on_view
+      redirect_to user.blocked_on_view, :referer => params[:referer]
+    elsif session[:referer]
+      redirect_to session[:referer]
+    else
+      redirect_to :controller => 'site', :action => 'index'
+    end
+
+    session.delete(:remember_me)
+    session.delete(:referer)
+  end
+
+  ##
+  # process a failed login
+  def failed_login(message)
+    flash[:error] = message
+
+    redirect_to :action => 'login', :referer =>  session[:referer]
+
+    session.delete(:remember_me)
+    session.delete(:referer)
+  end
+
+  ##
+  # update a user's details
+  def update_user(user)
+    if user.save
+      set_locale
+
+      if user.new_email.nil? or user.new_email.empty?
+        flash.now[:notice] = t 'user.account.flash update success'
+      else
+        flash.now[:notice] = t 'user.account.flash update success confirm needed'
+
+        begin
+          Notifier.deliver_email_confirm(user, user.tokens.create)
+        rescue
+          # Ignore errors sending email
+        end
+      end
+    end
+  end
+
   ##
   # require that the user is a administrator, or fill out a helpful error message
   # and return them to the user page.
index 0147c3fe6aabbf787808646d70952bb2f0bf1eed..8686d5a035c80751081aad9d3f3c1a1b89b2d037 100644 (file)
@@ -1,2 +1,16 @@
 module UserHelper
+  def openid_logo
+    image_tag "openid_small.png", :alt => t('user.login.openid_logo_alt'), :class => "openid_logo"
+  end
+
+  def openid_button(name, url)
+    link_to_function(
+      image_tag("#{name}.png", :alt => t("user.login.openid_providers.#{name}.alt")),
+      nil,
+      :title => t("user.login.openid_providers.#{name}.title")
+    ) do |page|
+      page[:login_form][:openid_url][:value] = url
+      page[:login_form].submit()
+    end
+  end
 end
index 31b0f27a03428ef9223b3c00bfe05bd89504c27f..f99ad3b40a8a51680b97f0b7c19b4fcd5504e13b 100644 (file)
@@ -23,6 +23,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..ba5b652ac92a5434afa9d57744b8b2e8c1380284 100644 (file)
     <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><%= f.text_field :openid_url, {:class => "openid_url"} %> <span class="minorNote">(<a href="<%= t 'user.account.openid.link' %>" target="_new"><%= t 'user.account.openid.link text' %></a>)</span></td>
+  </tr>
+
   <tr>
     <td class="fieldName" valign="top"><%= t 'user.account.public editing.heading' %></td>
     <td>
index cf7f4a8198097a2753e4bc4d20f46cc2de4f6d85..c2f801c15e3630c897e6107405ef752b04f81039 100644 (file)
@@ -2,13 +2,84 @@
 
 <p><%= t 'user.login.please login', :create_user_link => link_to(t('user.login.create_account'), :controller => 'user', :action => 'new', :referer => params[:referer]) %></p>
 
-<% form_tag :action => 'login' do %>
-<%= 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>
-</table>
+<% form_tag({ :action => "login" }, { :id => "login_form" }) do %>
+  <%= hidden_field_tag('referer', h(params[:referer])) %>
+
+  <div style="position: relative;">
+    <div class="loginBox">
+      <h3><%= t 'user.login.username_heading' %></h3>
+      <table>
+        <tr>
+          <td class="fieldName"><label for="user_email"><%= t 'user.login.email or username' %></label></td>
+          <td><%= text_field_tag("username", "", { :size => 28, :maxlength => 255, :tabindex => 1 }) %></td>
+        </tr>
+        <tr>
+          <td class="fieldName"><label for="user_password"><%= t 'user.login.password' %></label></td>
+          <td><%= password_field_tag("password", "", { :size => 28, :maxlength => 255, :tabindex => 2 }) %></td>
+        </tr>
+        <tr>
+          <td></td>
+          <td><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>
+      </table>
+
+      <%= submit_tag t('user.login.login_button'), :tabindex => 4 %>
+    </div>
+
+    <div style="float:left; width: 20px; padding: 10px;">
+    </div>
+
+    <div class="loginBox">
+      <h3><%= t 'user.login.openid_heading' %></h3>
+      <div id="openid_buttons">
+        <%=
+          link_to_function(image_tag("openid_large.png", :alt => t("user.login.openid_providers.openid.title")), nil, :title => t("user.login.openid_providers.openid.title")) do |page|
+            page[:login_form][:openid_url].value = "http://"
+            page[:openid_buttons].hide
+            page[:openid_url].show
+            page[:openid_url_hint].show
+            page[:openid_submit].show
+          end
+        %>
+        <%= openid_button "yahoo", "yahoo.com" %>
+        <%= openid_button "google", "gmail.com" %>
+        <%= openid_button "myopenid", "myopenid.com" %>
+        <%= openid_button "wordpress", "wordpress.com" %>
+        <%= openid_button "myspace", "myspace.com" %>
+      </div>
+
+      <table>
+        <tr id="openid_url">
+          <td class="fieldName">
+            <%= t 'user.login.openid', :logo => openid_logo %>
+          </td>
+          <td><%= text_field_tag("openid_url", "", { :size => 28, :maxlength => 255, :tabindex => 3, :class => "openid_url" }) %></td>
+        </tr>
+        <tr id="openid_url_hint">
+          <td></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 class="fieldName nowrap"><label for="remember_me"><%= t 'user.login.remember' %></label></td>
+          <td width="100%"><%= check_box_tag "remember_me", "yes", false, :tabindex => 5 %></td>
+        </tr>
+      </table>
+
+      <%= submit_tag t('user.login.login_button'), :tabindex => 6, :id => "openid_submit" %>
+    </div>
+  </div>
 <% end %>
+
+<%=
+  update_page_tag do |page|
+    page[:openid_url].hide
+    page[:openid_url_hint].hide
+    page[:openid_submit].hide
+  end
+%>
index 66d8826c2d4fcc0ed227a4208edecabb534b2704..4e3f4428d6f11194c339f31fa1d115ee1ddf4b23 100644 (file)
@@ -2,37 +2,89 @@
 
 <% if Acl.find_by_address(request.remote_ip, :conditions => {:k => "no_account_creation"}) %>
 
-<p><%= t 'user.new.no_auto_account_create' %>
-</p>
+<p><%= t 'user.new.no_auto_account_create' %></p>
 
-<p><%= t 'user.new.contact_webmaster' %>
-</p>
+<p><%= t 'user.new.contact_webmaster' %></p>
 
 <% else %>
 
-<p><%= t 'user.new.fill_form' %>
-</p>
+<p><%= t 'user.new.fill_form' %></p>
 
 <%= error_messages_for 'user' %>
 
 <% form_tag :action => 'terms' 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.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></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.continue' %>" tabindex="6"></td></tr>
-</table>
+  <%= hidden_field_tag('referer', h(@referer)) unless @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, :value => params[: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, :value => params[:email] }) %></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, :value => params[:nickname] }) %></td></tr>
+    <tr>
+      <td></td>
+      <td><span class="minorNote"><%= t 'user.new.display name description' %></span></td>
+    </tr>
+
+    <tr id="openid_spacer"><td colspan="2">&nbsp;<!--vertical spacer--></td></tr>
+
+    <tr id="openid_url">
+      <td class="fieldName"><%= t 'user.new.openid', :logo => openid_logo %></td>
+      <td><%= text_field(:user, :openid_url, { :size => 50, :maxlength => 255, :tabindex => 4, :value => params[:openid], :class => "openid_url" }) %></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 => 5 }) %></td>
+    </tr>
+    <tr>
+      <td class="fieldName"><%= t 'user.new.confirm password' %></td>
+      <td><%= password_field(:user, :pass_crypt_confirmation, { :size => 30, :maxlength => 255, :tabindex => 6 }) %></td>
+    </tr>
+    <tr>
+      <td></td>
+      <td>
+        <span id="openid_prompt" class="minorNote"><%= link_to_function(t('user.new.use openid', :logo => openid_logo)) { |page| page.hide 'openid_prompt'; page.show 'openid_spacer', 'openid_url', 'openid_note' } %></span>
+        <span id="openid_note" class="minorNote"><%= t 'user.new.openid no password' %></span>
+      </td>
+    </tr>
+
+    <tr><td colspan="2" >&nbsp;<!--vertical spacer--></td></tr>
+
+    <tr>
+      <td></td>
+      <td align="right"><%= submit_tag t('user.new.continue'), :tabindex => 6 %></td>
+    </tr>
+  </table>
 <% end %>
 
+<%=
+  update_page_tag do |page|
+    if params[:openid] or (@user and @user.openid_url)
+      page[:openid_prompt].hide
+    else
+      page[:openid_spacer].hide
+      page[:openid_url].hide
+      page[:openid_note].hide
+    end
+  end
+%>
+
 <%= javascript_include_tag 'https://ethnio.com/remotes/62786' %>
 
 <% end %>
index 049e07ca7d0b71adca4671446946e7fda4ca65ca..ae801bc11b45163b2b5918df487dbefc241ef99b 100644 (file)
@@ -38,6 +38,7 @@
     <%= hidden_field('user', 'display_name') %>
     <%= hidden_field('user', 'pass_crypt') %>
     <%= hidden_field('user', 'pass_crypt_confirmation') %>
+    <%= hidden_field('user', 'openid_url') %>
     <div id="buttons">
       <%= submit_tag(t('user.terms.decline'), :name => "decline", :id => "decline") %>
       <%= submit_tag(t('user.terms.agree'), :name => "agree", :id => "agree") %>
index 7ee9e79af53732ba61efb95ab7003e1d000a31b5..50e2ae3e24e5dd4faf38885e80e57d1e4bc7eb9d 100644 (file)
@@ -56,6 +56,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', :version => '>= 2.0.4', :lib => 'openid'
   config.gem 'SystemTimer', :version => '>= 1.1.3', :lib => 'system_timer'
   config.gem 'sanitize'
 
index 5abae7222ec5b3617d17e3ab10790e243ffa3af3..cc2dc5e980639f254e745c72e1fd8d917378ac01 100644 (file)
@@ -1485,12 +1485,37 @@ en:
       create_account: "create an account"
       email or username: "Email Address or Username:"
       password: "Password:"
+      openid: "{{logo}} OpenID:"
+      username_heading: "Login with username and password:"
+      openid_heading: "Login with OpenID:"
       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."
       account suspended: Sorry, your account has been suspended due to suspicious activity.<br />Please contact the <a href="mailto:webmaster@openstreetmap.org">webmaster</a> if you wish to discuss this.
       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 to be malformed"
+      openid_logo_alt: "Log in with an OpenID"
+      openid_providers:
+        openid:
+          title: Login with an OpenID URL
+          alt: Login with an OpenID URL
+        yahoo:
+          title: Login with a Yahoo! OpenID
+          alt: Login with a Yahoo! OpenID
+        google:
+          title: Login with a Google OpenID
+          alt: Login with a Google OpenID
+        myopenid:
+          title: Login with a myOpenID OpenID
+          alt: Login with a myOpenID OpenID
+        wordpress:
+          title: Login with a Wordpress.com OpenID
+          alt: Login with a Wordpress.com OpenID
+        myspace:
+          title: Login with a MySpace OpenID
+          alt: Login with a MySpace OpenID
     logout:
       title: "Logout"
       heading: "Logout from OpenStreetMap"
@@ -1523,8 +1548,21 @@ en:
       not displayed publicly: 'Not displayed publicly (see <a href="http://wiki.openstreetmap.org/wiki/Privacy_Policy" title="wiki privacy policy including section on email addresses">privacy policy</a>)'
       display name: "Display Name:"
       display name description: "Your publicly displayed username. You can change this later in the preferences."
+      openid: "{{logo}} OpenID:"
       password: "Password:"
       confirm password: "Confirm Password:"
+      use openid: "Alternatively, use {{logo}} OpenID to login"
+      openid no password: "With OpenID a password is not required, but some extra tools or server may still need one."
+      openid association: |
+        <p>Your OpenID is not associated with a OpenStreetMap account yet.</p>
+        <ul>
+          <li>If you are new to OpenStreetMap, please create a new account using the form below.</li>
+          <li>
+            If you already have an account, you can login to your account
+            using your username and password and then associate the account
+            with your OpenID in your user settings.
+          </li>
+        </ul> 
       continue: Continue
       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."
     terms:
@@ -1606,6 +1644,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."
index 60c88b15da2adb40df593f6e1f10c9a67b5ec7c6..0bc1c5fcef6db56c9464c1c1a1da773440d2ec71 100644 (file)
@@ -990,6 +990,8 @@ is:
       current email address: "Núverandi netfang:"
       delete image: Eyða þessari mynd
       email never displayed publicly: (aldrei sýnt opinberlega)
+      openid:
+        link text: "hvað er openID?"
       flash update success: Stillingarnar þínar voru uppfærðar.
       flash update success confirm needed: Stillingarnar þínar voru uppfærðar. Póstur var sendur á netfangið þitt sem þú þarft að bregðast við til að netfangið þitt verði staðfest.
       home location: "Staðsetning:"
@@ -1045,6 +1047,28 @@ is:
       please login: Vinsamlegast innskráðu þig eða {{create_user_link}}.
       remember: "Muna innskráninguna:"
       title: Innskrá
+      openid_heading: "Innskráning með OpenID:"
+      username_heading: "Innskráning með OpenStreetMap aðgang:"
+      openid_logo_alt: "Innskrá með OpenID"
+      openid_providers:
+        openid:
+          title: Innskrá með OpenID slóð
+          alt: Innskrá með OpenID slóð
+        yahoo:
+          title: Innsrká með Yahoo! OpenID
+          alt: Innsrká með Yahoo! OpenID
+        google:
+          title: Innsrká með Google OpenID
+          alt: Innsrká með Google OpenID
+        myopenid:
+          title: Innsrká með myOpenID OpenID
+          alt: Innsrká með myOpenID OpenID
+        wordpress:
+          title: Innsrká með Wordpress.com OpenID
+          alt: Innsrká með Wordpress.com OpenID
+        myspace:
+          title: Innsrká með MySpace OpenID
+          alt: Innsrká með MySpace OpenID
     logout: 
       heading: Útskrá
       logout_button: Útskrá
@@ -1075,6 +1099,21 @@ is:
       no_auto_account_create: Því miður getum við eki búið til reikning fyrir þig sjálfkrafa.
       not displayed publicly: Ekki sýnt opinberlega (sjá <a href="http://wiki.openstreetmap.org/index.php?uselang=is&title=Privacy_Policy" title="Meðferð persónuupplýsinga, þ.á.m. netfanga">meðferð persónuupplýsinga</a>)
       password: "Lykilorð:"
+      openID associate: "Tengja OpenID við þennan aðgang"
+      openID: "OpenID:"
+      openID description: '(Valfrjálst) Ef þú ert með <a href="http://wiki.openstreetmap.org/wiki/openID">OpenID</a> getur þú tengt það við nýja aðganginn þinn.'
+      openID nopassword: "Með OpenID þarft þú ekki að gefa upp lykilorð við innskráningu. Í stað þess notar þú OpenID."
+      openID association: |
+        Þetta OpenID er ekki tengt við neinn OpenStreetMap aðgang.
+        <ul>
+          <li>Ef þú ert ekki með OpenStreetMap aðgang getur þú búið til nýjan aðgang hér fyrir neðan.</li>
+          <li>
+            Ef þú ert þegar með aðgang skaltu innskrá þig með
+            honum. Svo getur þú tengt OpenID við aðganginn þinn á
+            stillingarsíðunni.
+          </li>
+        </ul> 
+      signup: Nýskrá
       title: Nýskrá
     no_such_user: 
       body: Það er ekki til notandi með nafninu {{user}}. Kannski slóstu nafnið rangt inn eða fylgdir ógildum tengli.
diff --git a/db/migrate/053_add_open_id_authentication_tables.rb b/db/migrate/053_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/public/images/google.png b/public/images/google.png
new file mode 100644 (file)
index 0000000..9ae3bd8
Binary files /dev/null and b/public/images/google.png differ
diff --git a/public/images/myopenid.png b/public/images/myopenid.png
new file mode 100644 (file)
index 0000000..78e4562
Binary files /dev/null and b/public/images/myopenid.png differ
diff --git a/public/images/myspace.png b/public/images/myspace.png
new file mode 100644 (file)
index 0000000..2fefe48
Binary files /dev/null and b/public/images/myspace.png differ
diff --git a/public/images/openid_input.png b/public/images/openid_input.png
new file mode 100644 (file)
index 0000000..b5aa49d
Binary files /dev/null and b/public/images/openid_input.png differ
diff --git a/public/images/openid_large.png b/public/images/openid_large.png
new file mode 100644 (file)
index 0000000..6f8bf63
Binary files /dev/null and b/public/images/openid_large.png differ
diff --git a/public/images/openid_small.png b/public/images/openid_small.png
new file mode 100644 (file)
index 0000000..83bb302
Binary files /dev/null and b/public/images/openid_small.png differ
diff --git a/public/images/wordpress.png b/public/images/wordpress.png
new file mode 100644 (file)
index 0000000..9015498
Binary files /dev/null and b/public/images/wordpress.png differ
diff --git a/public/images/yahoo.png b/public/images/yahoo.png
new file mode 100644 (file)
index 0000000..0a54c8a
Binary files /dev/null and b/public/images/yahoo.png differ
index 2590c2d5f655a3d456bb320c7447643884e9b83a..9d4ecb44515aef0e4c28347ed856fe2a49c58306 100644 (file)
@@ -583,6 +583,37 @@ hr {
   margin-top: 10px;
 }
 
+/* Rules for the login form */
+
+.loginBox {
+  float: left;
+  border-style: solid;
+  border-width: 1px;
+  padding-left: 10px;
+  padding-right: 10px;
+  padding-bottom: 10px;
+}
+
+.loginBox table {
+  width: 100%;
+}
+
+.loginBox img {
+  border: 0;
+}
+
+.loginBox #openid_buttons img {
+  vertical-align: middle;
+}
+
+.loginBox input[type="submit"] {
+  float: right;
+}
+
+#openid_buttons {
+  margin-bottom: 20px;
+}
+
 /* Rules for the account confirmation page */
 
 div#contributorTerms {
@@ -759,6 +790,11 @@ input[type="submit"] {
   border: 1px solid black;
 }
 
+input.openid_url { 
+  background: url('../images/openid_input.png') repeat-y left;
+  padding-left: 16px;
+}
+
 /* Rules for user images */
 
 img.user_image {
@@ -801,3 +837,10 @@ abbr.geo {
 .table1 { 
   background: #fff;
 }
+
+/* Rules for OpenID logo */
+
+.openid_logo {
+  vertical-align: text-bottom;
+  border: 0;
+}
index a1efa8583621c7b894955d708648f3cf3e2be61b..458099add6b855137b1800e8b75eb211882c5b98 100644 (file)
 .olControlPanZoom {
   display: none;
 }
+
+/* Rules for the login form */
+
+.loginBox {
+  width: 400px;
+  height: 200px;
+  margin-bottom: 40px;
+}
index b11aebf69cf0688b0e0e964be8aaa20e64371fb8..c709bcba9b05c9f91929b0242bd878cc8dd43f9e 100644 (file)
@@ -100,12 +100,21 @@ h1 {
 
 /* Rules for the login form */
 
-#loginForm input#user_email {
+.loginBox {
+  width: 90%;
+}
+
+.loginBox input#user_email {
+  width: 100%;
+  max-width: 18em;
+}
+
+.loginBox input#user_password {
   width: 100%;
   max-width: 18em;
 }
 
-#loginForm input#user_password {
+.loginBox input#user_openid_url {
   width: 100%;
   max-width: 18em;
 }
index eb3c6ef6d590547fd78b029dd0be720a805a3749..9802783fd0da0b7dfece5ca063f7ec3013497ad2 100644 (file)
@@ -68,3 +68,13 @@ administrator_user:
   creation_time: "2008-05-01 01:23:45"
   display_name: administrator
   data_public: true
+
+openid_user:
+  id: 7
+  email: openid-user@example.com
+  status: active
+  pass_crypt: <%= Digest::MD5.hexdigest('test') %>
+  creation_time: "2008-05-01 01:23:45"
+  display_name: openIDuser
+  data_public: true
+  openid_url: http://localhost:1123/john.doe?openid.success=true
index 8e08cbda0f9da15d8df3d46bb5896b519285a8d1..80da36deb38edb975e5753dcbf465c24fe9adf3a 100644 (file)
@@ -12,7 +12,7 @@ class ClientApplicationTest < ActionController::IntegrationTest
     assert_redirected_to "controller" => "user", "action" => "login", "cookie_test" => "true"
     follow_redirect!
     assert_response :success
-    post '/login', {'user[email]' => "test@example.com", 'user[password]' => "test", :referer => '/user/test2'}
+    post '/login', {'username' => "test@example.com", 'password' => "test", :referer => '/user/test2'}
     assert_response :redirect
     follow_redirect!
     assert_response :success
index ecd1d37c5204fe0cc87acee8dcc3d31fa3bd415a..942a94302de8ec4195f2afdb46a0545aa3573af6 100644 (file)
@@ -42,7 +42,7 @@ class UserBlocksTest < ActionController::IntegrationTest
     assert_redirected_to "controller" => "user", "action" => "login", "cookie_test" => "true"
     follow_redirect!
     assert_response :success
-    post '/login', {'user[email]' => moderator.email, 'user[password]' => "test", :referer => "/blocks/#{block.id}/revoke"}
+    post '/login', {'username' => moderator.email, 'password' => "test", :referer => "/blocks/#{block.id}/revoke"}
     assert_response :redirect
     follow_redirect!
     assert_response :success
index 77d73552a82ef100d4e04eeb4746af540e5a910c..3a9795bc703ac88e79d80857f61942da31f413a9 100644 (file)
@@ -94,8 +94,12 @@ class UserCreationTest < ActionController::IntegrationTest
     referer = "/traces/mine"
     assert_difference('User.count') do
       assert_difference('ActionMailer::Base.deliveries.size', 1) do
-        post_via_redirect "/user/save",
+        post "/user/terms",
         {:user => { :email => new_email, :email_confirmation => new_email, :display_name => display_name, :pass_crypt => password, :pass_crypt_confirmation => password}, :referer => referer }
+        assert_response :success
+        assert_template 'terms'
+        post_via_redirect "/user/save",
+        {:user => { :email => new_email, :email_confirmation => new_email, :display_name => display_name, :pass_crypt => password, :pass_crypt_confirmation => password} }
       end
     end
 
@@ -127,4 +131,33 @@ class UserCreationTest < ActionController::IntegrationTest
     assert_response :success
     assert_template "trace/list.html.erb"
   end
+
+  def test_user_create_openid_success
+    new_email = "newtester-openid@osm.org"
+    display_name = "new_tester-openid"
+    password = "testtest"
+    assert_difference('User.count') do
+      assert_difference('ActionMailer::Base.deliveries.size', 1) do
+        post "/user/terms",
+          {:user => { :email => new_email, :email_confirmation => new_email, :display_name => display_name, :openid_url => "http://localhost:1123/john.doe?openid.success=newuser", :pass_crypt => "", :pass_crypt_confirmation => ""}}
+        assert_response :redirect
+        res = openid_request(@response.redirected_to)
+        post '/user/terms', res
+        assert_response :success
+        assert_template 'terms'
+        post '/user/save',
+          {:user => { :email => new_email, :email_confirmation => new_email, :display_name => display_name, :openid_url => "http://localhost:1123/john.doe?openid.success=newuser", :pass_crypt => password, :pass_crypt_confirmation => password}}
+        assert_response :redirect
+        follow_redirect!
+      end
+    end
+
+    # Check the page
+    assert_response :success
+    assert_template 'login'
+
+    ActionMailer::Base.deliveries.clear
+  end
+
+
 end
index b686fbac9132d858d6165c3ef33127418efee62a..fab05894fe924a6b55f48629c6bec2d0fa291339 100644 (file)
@@ -11,7 +11,7 @@ class UserDiariesTest < ActionController::IntegrationTest
     assert_response :success
     assert_template 'user/login'
     # We can now login
-    post  '/login', {'user[email]' => "test@openstreetmap.org", 'user[password]' => "test", :referer => '/diary/new'}
+    post  '/login', {'username' => "test@openstreetmap.org", 'password' => "test", :referer => '/diary/new'}
     assert_response :redirect
     #print @response.body
     # Check that there is some payload alerting the user to the redirect
diff --git a/test/integration/user_login_test.rb b/test/integration/user_login_test.rb
new file mode 100644 (file)
index 0000000..a272f7b
--- /dev/null
@@ -0,0 +1,87 @@
+require File.dirname(__FILE__) + '/../test_helper'
+
+class UserLoginTest < ActionController::IntegrationTest
+  fixtures :users
+
+  def test_login_openid_success
+    get '/login'
+    assert_response :redirect
+    assert_redirected_to "controller" => "user", "action" => "login", "cookie_test" => "true"
+    follow_redirect!
+    assert_response :success
+    post '/login', {'openid_url' => "http://localhost:1123/john.doe?openid.success=true", :referer => "/browse"}
+    assert_response :redirect
+
+    res = openid_request(@response.redirected_to)
+    res2 = post '/login', res
+
+    assert_response :redirect
+    follow_redirect!
+    assert_response :success
+    assert_template 'changeset/list'
+  end
+
+  def test_login_openid_cancel
+    get '/login'
+    assert_response :redirect
+    assert_redirected_to "controller" => "user", "action" => "login", "cookie_test" => "true"
+    follow_redirect!
+    assert_response :success
+    post '/login', {'openid_url' => "http://localhost:1123/john.doe", :referer => "/diary"}
+    assert_response :redirect
+
+    res = openid_request(@response.redirected_to)
+    post '/login', res
+
+    assert_response :redirect
+    follow_redirect!
+    assert_response :success
+    assert_template 'login'
+  end
+
+  def test_login_openid_invalid_provider
+    get '/login'
+    assert_response :redirect
+    assert_redirected_to "controller" => "user", "action" => "login", "cookie_test" => "true"
+    follow_redirect!
+    assert_response :success
+    #Use a different port that doesn't have the OpenID provider running on to test an invalid openID
+    post '/login', {'openid_url' => "http://localhost:1124/john.doe", :referer => "/diary"}
+    assert_response :redirect
+    follow_redirect!
+    assert_response :success
+    assert_template 'login'
+  end
+
+  def test_login_openid_invalid_url
+    get '/login'
+    assert_response :redirect
+    assert_redirected_to "controller" => "user", "action" => "login", "cookie_test" => "true"
+    follow_redirect!
+    assert_response :success
+    #Use a url with an invalid protocol to make sure it handles that correctly too
+    post '/login', {'openid_url' => "htt://localhost:1123/john.doe", :referer => "/diary"}
+    assert_response :redirect
+    follow_redirect!
+    assert_response :success
+    assert_template 'login'
+  end
+
+  def test_login_openid_unknown
+    get '/login'
+    assert_response :redirect
+    assert_redirected_to "controller" => "user", "action" => "login", "cookie_test" => "true"
+    follow_redirect!
+    assert_response :success
+    post '/login', {'openid_url' => "http://localhost:1123/john.doe?openid.success=true_somethingelse", :referer => "/diary"}
+    assert_response :redirect
+
+    res = openid_request(@response.redirected_to)
+    res2 = post '/login', res
+
+    assert_response :redirect
+    follow_redirect!
+    assert_response :success
+    assert_template 'user/new'
+  end
+end
index 0691edc8ea0e763436cc5ca7e0e218ede1022d66..8bf06374fc41601ed914b0197d2d51712cc26dd4 100644 (file)
@@ -22,7 +22,7 @@ class UserRolesControllerTest < ActionController::IntegrationTest
     assert_redirected_to "controller" => "user", "action" => "login", "cookie_test" => "true"
     follow_redirect!
     assert_response :success
-    post '/login', {'user[email]' => users(user).email, 'user[password]' => "test", :referer => "/"}
+    post '/login', {'username' => users(user).email, 'password' => "test", :referer => "/"}
     assert_response :redirect
     follow_redirect!
     assert_response :success
@@ -40,7 +40,7 @@ class UserRolesControllerTest < ActionController::IntegrationTest
     assert_redirected_to "controller" => "user", "action" => "login", "cookie_test" => "true"
     follow_redirect!
     assert_response :success
-    post '/login', {'user[email]' => users(user).email, 'user[password]' => "test", :referer => "/"}
+    post '/login', {'username' => users(user).email, 'password' => "test", :referer => "/"}
     assert_response :redirect
     follow_redirect!
     assert_response :success
index 4972ee6d6e6b90b1d7fb14c8f2e54ed687fdf9e0..a83a89f2ab8f56979c3db9c2d573fbfff2d3cad2 100644 (file)
@@ -141,6 +141,15 @@ class ActiveSupport::TestCase
   def assert_no_missing_translations(msg="")
     assert_select "span[class=translation_missing]", false, "Missing translation #{msg}"
   end
+
+  def openid_request(openid_request_uri)
+    openid_response = Net::HTTP.get_response(URI.parse(openid_request_uri))
+    openid_response_uri = URI(openid_response['Location'])
+    openid_response_qs = Rack::Utils.parse_query(openid_response_uri.query)
+
+    return openid_response_qs
+  end
+
   
   # Add more helper methods to be used by all tests here...
 end
diff --git a/vendor/gems/rots-0.2.1/AUTHORS b/vendor/gems/rots-0.2.1/AUTHORS
new file mode 100644 (file)
index 0000000..ad2dd89
--- /dev/null
@@ -0,0 +1,2 @@
+* Roman Gonzalez <romanandreg@gmail.com>
+* Anibal Rojas <anibal@rojas.net.ve>
\ No newline at end of file
diff --git a/vendor/gems/rots-0.2.1/README b/vendor/gems/rots-0.2.1/README
new file mode 100644 (file)
index 0000000..5369cc6
--- /dev/null
@@ -0,0 +1,64 @@
+= Ruby OpenID Test Server (ROTS), a dummy OpenID server that makes consumer tests dead easy.
+
+ROTS is a minimal implementation of an OpenID server, developed on top of the Rack middleware, this
+server provides an easy to use interface to make testing OpenID consumers really easy.
+
+== No more mocks
+
+Have you always wanted to test the authentication of an OpenID consumer implementation, but find your self 
+in a point where is to hard to mock? A lot of people have been there. 
+
+With ROTS, you only need to specify an identity url provided by the dummy server, passing with it a flag
+saying that you want the authentication to be successful. It handles SREG extensions as well.
+
+== How does it works
+
+When you install the ROTS gem, a binary called rots is provided for starting the server (for more
+info about what options you have when executing this file, check the -h option). 
+
+By default, rots will have a test user called "John Doe", with an OpenID identity "john.doe". 
+If you want to use your own test user name, you can specify a config file to rots. The
+default configuration file looks like this:
+
+# Default configuration file
+identity: john.doe
+sreg:
+  nickname: jdoe
+  fullname: John Doe
+  email: jhon@doe.com
+  dob: 1985-09-21
+  gender: M
+
+You can specify a new config file using the option --config.
+
+== Getting Started
+
+The best way to get started, is running the rots server, and then starting to execute your OpenID consumer tests/specs. You just have to specify the identity url of your test user, if you want the OpenID response be successful just add the openid.success=true flag to the user identity url. If you don't specify the flag it 
+will return a cancel response instead.
+
+Example:
+
+it "should authenticate with OpenID" do
+  post("/consumer_openid_login", 'identity_url' => 'http://localhost:1132/john.doe?openid.success=true')
+end
+
+== Copyright
+
+Copyright (C) 2009 Roman Gonzalez <romanandreg@gmail.com>
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to
+deal in the Software without restriction, including without limitation the
+rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+sell copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 
+IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/vendor/gems/rots-0.2.1/Rakefile b/vendor/gems/rots-0.2.1/Rakefile
new file mode 100644 (file)
index 0000000..7dd59be
--- /dev/null
@@ -0,0 +1,125 @@
+# Rakefile for Rack.  -*-ruby-*-
+require 'rake/rdoctask'
+require 'rake/testtask'
+require 'spec/rake/spectask'
+
+
+desc "Run all the tests"
+task :default => [:spec]
+
+desc "Do predistribution stuff"
+task :predist => [:changelog, :rdoc]
+
+
+desc "Make an archive as .tar.gz"
+task :dist => [:fulltest, :predist] do
+  sh "git archive --format=tar --prefix=#{release}/ HEAD^{tree} >#{release}.tar"
+  sh "pax -waf #{release}.tar -s ':^:#{release}/:' RDOX SPEC ChangeLog doc"
+  sh "gzip -f -9 #{release}.tar"
+end
+
+# Helper to retrieve the "revision number" of the git tree.
+def git_tree_version
+  #if File.directory?(".git")
+  #  @tree_version ||= `git describe`.strip.sub('-', '.')
+  #  @tree_version << ".0"  unless @tree_version.count('.') == 2
+  #else
+    $: << "lib"
+    require 'rots'
+    @tree_version = Rots.release
+  #end
+  @tree_version
+end
+
+def gem_version
+  git_tree_version.gsub(/-.*/, '')
+end
+
+def release
+  "ruby-openid-tester-#{git_tree_version}"
+end
+
+def manifest
+  `git ls-files`.split("\n")
+end
+
+desc "Generate a ChangeLog"
+task :changelog do
+  File.open("ChangeLog", "w") do |out|
+    `git log -z`.split("\0").map do |chunk|
+      author = chunk[/Author: (.*)/, 1].strip
+      date   = chunk[/Date: (.*)/, 1].strip
+      desc, detail = $'.strip.split("\n", 2)
+      detail ||= ""
+      detail.rstrip!
+      out.puts "#{date}  #{author}"
+      out.puts "  * #{desc.strip}"
+      out.puts detail  unless detail.empty?
+      out.puts
+    end
+  end
+end
+
+
+begin
+  require 'rubygems'
+
+  require 'rake'
+  require 'rake/clean'
+  require 'rake/packagetask'
+  require 'rake/gempackagetask'
+  require 'fileutils'
+rescue LoadError
+  # Too bad.
+else
+  spec = Gem::Specification.new do |s|
+    s.name            = "rots"
+    s.version         = gem_version
+    s.platform        = Gem::Platform::RUBY
+    s.summary         = "an OpenID server for making tests of OpenID clients implementations"
+
+    s.description = <<-EOF
+Ruby OpenID Test Server (ROST) provides a basic OpenID server made in top of the Rack gem.
+With this small server, you can make dummy OpenID request for testing purposes,
+the success of the response will depend on a parameter given on the url of the authentication request.
+    EOF
+
+    s.files           = manifest
+    s.bindir          = 'bin'
+    s.executables     << 'rots'
+    s.require_path    = 'lib'
+    s.has_rdoc        = true
+    s.extra_rdoc_files = ['README']
+    s.test_files      = Dir['spec/*_spec.rb']
+
+    s.author          = 'Roman Gonzalez'
+    s.email           = 'romanandreg@gmail.com'
+    s.homepage        = 'http://github.com/roman'
+    s.rubyforge_project = 'rots'
+
+    s.add_development_dependency 'rspec'
+    s.add_development_dependency 'rack'
+    s.add_development_dependency 'ruby-openid', '~> 2.0.0'
+  end
+
+  Rake::GemPackageTask.new(spec) do |p|
+    p.gem_spec = spec
+    p.need_tar = false
+    p.need_zip = false
+  end
+end
+
+Spec::Rake::SpecTask.new do |t|
+end
+
+desc "Generate RDoc documentation"
+Rake::RDocTask.new(:rdoc) do |rdoc|
+  rdoc.options << '--line-numbers' << '--inline-source' <<
+    '--main' << 'README' <<
+    '--title' << 'ROTS Documentation' <<
+    '--charset' << 'utf-8'
+  rdoc.rdoc_dir = "doc"
+  rdoc.rdoc_files.include 'README'
+  rdoc.rdoc_files.include('lib/ruby_openid_test_server.rb')
+  rdoc.rdoc_files.include('lib/ruby_openid_test_server/*.rb')
+end
diff --git a/vendor/gems/rots-0.2.1/bin/rots b/vendor/gems/rots-0.2.1/bin/rots
new file mode 100755 (executable)
index 0000000..726c655
--- /dev/null
@@ -0,0 +1,95 @@
+#!/usr/bin/env ruby
+# -*- ruby -*-
+
+$:.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
+require "rubygems"
+require "optparse"
+require "rack"
+require "yaml"
+require "rots"
+
+server_options = {
+  :debugger => false,
+  :port => 1123,
+  :verbose => true,
+  :storage => File.join('.', 'tmp', 'rots'),
+  :config => <<-DEFAULT_CONFIG
+# Default configuration file
+identity: john.doe
+sreg:
+  nickname: jdoe
+  fullname: John Doe
+  email: jhon@doe.com
+  dob: 1985-09-21
+  gender: M
+
+  DEFAULT_CONFIG
+}
+
+opts = OptionParser.new do |opts|
+  opts.banner = "Usage: rots [options]"
+  
+  opts.separator ""
+  opts.separator "Options:"
+  
+  opts.on("-p", "--port PORT",
+            "use PORT (default: 1123)") do |port|
+    server_options[:port] = port
+  end
+  
+  opts.on("-s", "--storage PATH",
+            "use PATH as the OpenID Server storage path (default: ./tmp/rots)") do |storage_path|
+    server_options[:storage] = storage_path
+  end
+  
+  opts.on("-c", "--config FILE.yaml",
+            "server configuration YAML file") do |config_path|
+    abort "\x1B[31mConfiguration file #{config_path} not found\x1B[0m" unless File.exists?(config_path)
+    server_options[:config] = File.new(config_path)
+  end
+  
+  opts.on("-s", "--silent",
+            "If specified, the server will be in silent mode") do 
+    server_options[:verbose] = false
+  end
+  
+  opts.on("-d", "--debugger") do
+    server_options[:debugger] = true
+  end
+  
+  opts.separator ""
+  opts.separator "Common options:"
+  
+  opts.on_tail("-h", "--help", "Show this help message") do
+    puts opts
+    exit
+  end
+  
+end
+
+opts.parse!(ARGV)
+
+config = YAML.load(server_options[:config])
+
+require "ruby-debug" if server_options[:debugger]
+
+server = Rack::Builder.new do 
+  use Rack::Lint
+  if server_options[:verbose]
+    use Rack::CommonLogger, STDOUT
+    use Rack::ShowExceptions
+  end
+  map ("/%s" % config['identity']) do
+    run Rots::IdentityPageApp.new(config, server_options)
+  end
+  map "/server" do
+    run Rots::ServerApp.new(config, server_options)
+  end
+end
+
+puts "\x1B[32mRunning OpenID Test server on port 1123\x1B[0m" if server_options[:verbose]
+begin 
+  Rack::Handler::Mongrel.run server, :Port => server_options[:port]
+rescue LoadError
+  Rack::Handler::WEBrick.run server, :Port => server_options[:port]
+end
diff --git a/vendor/gems/rots-0.2.1/lib/rots.rb b/vendor/gems/rots-0.2.1/lib/rots.rb
new file mode 100644 (file)
index 0000000..644416c
--- /dev/null
@@ -0,0 +1,11 @@
+module Rots
+  
+  def self.release
+    "0.2.1"
+  end
+  
+end
+
+require "rots/server_app"
+require "rots/identity_page_app"
+require "rots/test_helper"
diff --git a/vendor/gems/rots-0.2.1/lib/rots/identity_page_app.rb b/vendor/gems/rots-0.2.1/lib/rots/identity_page_app.rb
new file mode 100644 (file)
index 0000000..09d70db
--- /dev/null
@@ -0,0 +1,36 @@
+require 'rack/request'
+require 'rack/response'
+require 'rack/utils'
+require 'openid'
+
+class Rots::IdentityPageApp 
+  
+  def initialize(config, server_options)
+    @server_options = server_options
+    @config = config
+  end
+  
+  def call(env)
+    @request = Rack::Request.new(env)
+    Rack::Response.new do |response|
+      response.write <<-HERE
+<html>
+  <head>
+  <link rel="openid2.provider" href="#{op_endpoint}" />
+  <link rel="openid.server" href="#{op_endpoint}" />
+  </head>
+  <body>
+    <h1>This is #{@config['identity']} identity page</h1>
+  </body>
+</html>
+      HERE
+    end.finish
+  end
+  
+  def op_endpoint
+    "http://%s:%d/server/%s" % [@request.host, 
+                           @request.port, 
+                           (@request.params['openid.success'] ? '?openid.success=true' : '')]
+  end
+  
+end
\ No newline at end of file
diff --git a/vendor/gems/rots-0.2.1/lib/rots/server_app.rb b/vendor/gems/rots-0.2.1/lib/rots/server_app.rb
new file mode 100644 (file)
index 0000000..e08595c
--- /dev/null
@@ -0,0 +1,147 @@
+require 'openid'
+require 'openid/extension'
+require 'openid/extensions/sreg'
+require 'openid/store/filesystem'
+require 'openid/util'
+require 'rack/request'
+require 'rack/utils'
+require 'fileutils'
+
+
+module Rots
+  
+  class ServerApp
+    
+    attr_accessor :request,:openid_request,
+                  :response, :openid_response,
+                  :server
+    
+    def initialize(config, server_options)
+      @server_options = server_options
+      @sreg_fields = config['sreg']
+    end
+    
+    def call(env)
+      on_openid_request(env) do
+        if !is_checkid_request?
+          @openid_response = @server.handle_request(@openid_request)
+          reply_consumer
+        elsif is_checkid_immediate?
+          process_immediate_checkid_request
+        else
+          process_checkid_request
+        end
+      end
+    end
+    
+    protected
+    
+    def on_openid_request(env)
+      create_wrappers(env)
+      if @openid_request.nil?
+        [200, {'Content-Type' => 'text/html'}, 
+          ["<html><body><h1>ROTS => This is an OpenID endpoint</h1></body></html>"] ]
+      else
+        yield
+      end
+    end
+    
+    def create_wrappers(env)
+      @request = Rack::Request.new(env)
+      @server  = OpenID::Server::Server.new(storage, op_endpoint)
+      @openid_request = @server.decode_request(@request.params)
+      @openid_sreg_request = OpenID::SReg::Request.from_openid_request(@openid_request) unless @openid_request.nil?
+    end
+    
+    def is_checkid_request?
+      @openid_request.is_a?(OpenID::Server::CheckIDRequest)
+    end
+    
+    def is_checkid_immediate?
+      @openid_request && @openid_request.immediate
+    end
+    
+    def process_immediate_checkid_request
+      # TODO: We should enable the user to configure
+      # if she wants immediate request support or not
+      url = OpenID::Util.append_args(@openid_request.return_to, 
+        @request.params.merge('openid.mode' => 'setup_needed'))
+      redirect(url)
+    end
+    
+    def process_checkid_request
+      if checkid_request_is_valid?
+        return_successful_openid_response
+      else
+        return_cancel_openid_response
+      end
+    end
+    
+    def checkid_request_is_valid?
+      @request.params['openid.success'] == 'true'
+    end
+    
+    def return_successful_openid_response
+      @openid_response = @openid_request.answer(true)
+      process_sreg_extension
+      # TODO: Add support for SREG extension
+      @server.signatory.sign(@openid_response) if @openid_response.needs_signing
+      reply_consumer
+    end
+    
+    def process_sreg_extension
+      return if @openid_sreg_request.nil?
+      response = OpenID::SReg::Response.extract_response(@openid_sreg_request, @sreg_fields)
+      @openid_response.add_extension(response)
+    end
+    
+    def return_cancel_openid_response
+      redirect(@openid_request.cancel_url)
+    end
+    
+    def reply_consumer
+      web_response = @server.encode_response(@openid_response)
+      case web_response.code
+      when OpenID::Server::HTTP_OK
+        success(web_response.body)
+      when OpenID::Server::HTTP_REDIRECT
+        redirect(web_response.headers['location'])
+      else
+        bad_request
+      end   
+    end
+
+    def redirect(uri)
+      [ 303, {'Content-Length'=>'0', 'Content-Type'=>'text/plain',
+        'Location' => uri},
+        [] ]
+    end
+
+    def bad_request()
+      [ 400, {'Content-Type'=>'text/plain', 'Content-Length'=>'0'},
+        [] ]
+    end
+    
+    def storage
+      # create the folder if it doesn't exist
+      FileUtils.mkdir_p(@server_options[:storage]) unless File.exist?(@server_options[:storage])
+      OpenID::Store::Filesystem.new(@server_options[:storage])
+    end
+    
+    def success(text="")
+      Rack::Response.new(text).finish
+    end
+    
+    def op_endpoint
+      if @request.url =~ /(.*\?openid.success=true)/
+        $1
+      elsif @request.url =~ /([^?]*)/
+        $1
+      else
+        nil
+      end
+    end
+
+  end
+
+end
\ No newline at end of file
diff --git a/vendor/gems/rots-0.2.1/lib/rots/test_helper.rb b/vendor/gems/rots-0.2.1/lib/rots/test_helper.rb
new file mode 100644 (file)
index 0000000..a7a91de
--- /dev/null
@@ -0,0 +1,16 @@
+require "openid/consumer"
+require "openid/consumer/checkid_request.rb"
+require "net/http"
+
+module Rots::TestHelper
+  
+  def openid_request(openid_request_uri)
+    openid_response = Net::HTTP.get_response(URI.parse(openid_request_uri))
+    openid_response_uri = URI(openid_response['Location'])
+    openid_response_qs = Rack::Utils.parse_query(openid_response_uri.query)
+    
+    { :url => openid_response_uri.to_s,
+      :query_params => openid_response_qs }
+  end
+  
+end
\ No newline at end of file
diff --git a/vendor/gems/rots-0.2.1/rots.gemspec b/vendor/gems/rots-0.2.1/rots.gemspec
new file mode 100644 (file)
index 0000000..0de2410
--- /dev/null
@@ -0,0 +1,31 @@
+# -*- encoding: utf-8 -*-
+
+Gem::Specification.new do |s|
+  s.name            = "rots"
+  s.version         = '0.2.1'
+  s.platform        = Gem::Platform::RUBY
+  s.summary         = "an OpenID server for making tests of OpenID clients implementations"
+
+  s.description = <<-EOF
+Ruby OpenID Test Server (ROST) provides a basic OpenID server made in top of the Rack gem.
+With this small server, you can make dummy OpenID request for testing purposes,
+the success of the response will depend on a parameter given on the url of the authentication request.
+  EOF
+
+  s.files           = ["AUTHORS", "README", "Rakefile", "bin/rots", "lib/rots.rb", "lib/rots/identity_page_app.rb", "lib/rots/server_app.rb", "lib/rots/test_helper.rb","rots.gemspec", "spec/server_app_spec.rb", "spec/spec_helper.rb"] 
+  s.bindir          = 'bin'
+  s.executables     << 'rots'
+  s.require_path    = 'lib'
+  s.has_rdoc        = true
+  s.extra_rdoc_files = ['README']
+  s.test_files      = ['spec/server_app_spec.rb', 'spec/spec_helper.rb']
+
+  s.author          = 'Roman Gonzalez'
+  s.email           = 'romanandreg@gmail.com'
+  s.homepage        = 'http://github.com/roman'
+  s.rubyforge_project = 'rots'
+
+  s.add_development_dependency 'rspec'
+  s.add_development_dependency 'rack'
+  s.add_development_dependency 'ruby-openid'
+end
diff --git a/vendor/gems/rots-0.2.1/spec/server_app_spec.rb b/vendor/gems/rots-0.2.1/spec/server_app_spec.rb
new file mode 100644 (file)
index 0000000..f76d689
--- /dev/null
@@ -0,0 +1,99 @@
+require File.join(File.dirname(__FILE__), 'spec_helper')
+
+# This is just a comment test
+
+describe Rots::ServerApp do
+
+  describe "when the request is not an OpenID request" do
+
+    it "should return a helpful message saying that is an OpenID endpoint" do
+      request  = Rack::MockRequest.new(Rots::ServerApp.new({'sreg' => {}}, 
+        {:storage => File.join(*%w(. tmp rots)) }))
+      response = request.get("/")
+      response.should be_ok
+      response.body.should == "<html><body><h1>ROTS => This is an OpenID endpoint</h1></body></html>"
+    end
+
+  end
+
+  describe "when the request is an OpenID request" do
+    
+    before(:each) do
+      @request = Rack::MockRequest.new(Rots::ServerApp.new({
+        'identity' => 'john.doe',
+        'sreg' => {
+          'email' => "john@doe.com",
+          'nickname' => 'johndoe',
+          'fullname' => "John Doe",
+          'dob' => "1985-09-21",
+          'gender' => "M"
+        }},
+        {:storage => File.join(*%w(. tmp rots))}
+      ))
+    end
+    
+
+    describe "and it is a check_id request" do
+
+      describe "and is immediate" do
+
+        it "should return an openid.mode equal to setup_needed" do
+          response = checkid_immediate(@request)
+          params = openid_params(response)
+          params['openid.mode'].should == 'setup_needed'
+        end
+
+      end
+
+      describe "and is not immediate" do
+
+        describe "with a success flag" do
+
+          it "should return an openid.mode equal to id_res" do
+            response = checkid_setup(@request, 'openid.success' => 'true')
+            params = openid_params(response)
+            params['openid.mode'].should == 'id_res'
+          end
+
+        end
+
+        describe "without a success flag" do
+
+          it "should return an openid.mode equal to cancel" do
+            response = checkid_setup(@request)
+            params = openid_params(response)
+            params['openid.mode'].should == 'cancel'
+          end
+
+        end
+        
+        describe "using SREG extension with a success flag" do
+          
+          it "should return an openid.mode equal to id_res" do
+            response = checkid_setup(@request, 'openid.success' => 'true')
+            params = openid_params(response)
+            params['openid.mode'].should == 'id_res'
+          end
+          
+          it "should return all the sreg fields" do
+            response = checkid_setup(@request, {
+              'openid.success' => true,
+              'openid.ns.sreg' => OpenID::SReg::NS_URI,
+              'openid.sreg.required' => 'email,nickname,fullname',
+              'openid.sreg.optional' => 'dob,gender'
+            })
+            params = openid_params(response)
+            params['openid.sreg.email'].should == "john@doe.com"
+            params['openid.sreg.nickname'].should == 'johndoe'
+            params['openid.sreg.fullname'].should == "John Doe"
+            params['openid.sreg.dob'].should == "1985-09-21"
+            params['openid.sreg.gender'].should == "M"
+          end
+          
+        end
+      
+      end
+    end
+  end
+
+end
\ No newline at end of file
diff --git a/vendor/gems/rots-0.2.1/spec/spec_helper.rb b/vendor/gems/rots-0.2.1/spec/spec_helper.rb
new file mode 100644 (file)
index 0000000..3dae012
--- /dev/null
@@ -0,0 +1,73 @@
+$:.unshift(File.dirname(__FILE__), '..', 'lib')
+require "rubygems"
+require "spec"
+require "rack"
+require "rots"
+
+module Rots::RequestHelper
+  
+  def checkid_setup(request, params={}, with_associate=true)
+    assoc_handle = make_association(request) if with_associate
+    send_checkid(request, :setup, params, assoc_handle)
+  end
+  
+  def checkid_immediate(request, params={}, with_associate=true)
+    assoc_handle = make_association(request) if with_associate
+    send_checkid(request, :immediate, params, assoc_handle)
+  end
+  
+  def openid_params(response)
+    uri = URI(response.headers['Location'])
+    Rack::Utils.parse_query(uri.query)
+  end
+  
+  protected
+  
+  def send_checkid(request, mode, params={}, assoc_handle = nil)
+    params = self.send(:"checkid_#{mode}_params", params)
+    params.merge('openid.assoc_handle' => assoc_handle) if assoc_handle
+    qs = "/?" + Rack::Utils.build_query(params)
+    request.get(qs)
+  end
+
+  def make_association(request)
+    associate_qs = Rack::Utils.build_query(associate_params)
+    response = request.post('/', :input => associate_qs)
+    parse_assoc_handle_from(response)
+  end
+  
+  def parse_assoc_handle_from(response)
+    response.body.split("\n")[0].match(/^assoc_handle:(.*)$/).captures[0]
+  end
+  
+  def checkid_setup_params(params = {})
+    {
+      "openid.ns" => "http://specs.openid.net/auth/2.0",
+      "openid.mode" => "checkid_setup",
+      "openid.claimed_id" => 'john.doe',
+      "openid.identity" => 'john.doe',
+      "openid.return_to" => "http://www.google.com"
+      # need to specify the openid_handle by hand
+    }.merge!(params)
+  end
+  
+  def checkid_immediate_params(params = {})
+    checkid_setup_params({'openid.mode' => 'checkid_immediate'}.merge!(params))
+  end
+  
+  def associate_params
+    {
+      "openid.ns" => "http://specs.openid.net/auth/2.0",
+      "openid.mode" => "associate",
+      "openid.session_type" => "DH-SHA1",
+      "openid.assoc_type" => "HMAC-SHA1",
+      "openid.dh_consumer_public" =>
+      "U672/RsDUNxAFFAXA+ShVh5LMD2CRdsoqdqhDCPUzfCNy2f44uTWuid/MZuGfJmiVA7QmxqM3GSb8EVq3SGK8eGEwwyzUtatqHidx72rfwAav5AUrZTnwSPQJyiCFrKNGmNhXdRJzcfzSkgaC3hVz2kpADzEevIExG6agns1sYY="
+    }
+  end
+  
+end
+
+Spec::Runner.configure do |config|
+  config.include Rots::RequestHelper
+end
\ No newline at end of file
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"