]> git.openstreetmap.org Git - rails.git/commitdiff
Merge branch 'master' into openid
authorTom Hughes <tom@compton.nu>
Fri, 10 Jun 2011 18:11:08 +0000 (19:11 +0100)
committerTom Hughes <tom@compton.nu>
Fri, 10 Jun 2011 18:11:08 +0000 (19:11 +0100)
46 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/initializers/openid.rb [new file with mode: 0644]
config/locales/en.yml
config/locales/is.yml
db/migrate/20100516124737_add_open_id.rb [new file with mode: 0644]
public/images/aol.png [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/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/.specification [new file with mode: 0644]
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/init.rb [new file with mode: 0644]
vendor/plugins/open_id_authentication/lib/open_id_authentication.rb [new file with mode: 0644]

index 283e11936f55b2b5ec682f7c672e6f863966fcec..9e2961df2716fa95696d1efabf92cf5ec57c04ea 100644 (file)
@@ -27,22 +27,53 @@ class UserController < ApplicationController
       render :update do |page|
         page.replace_html "contributorTerms", :partial => "terms"
       end
+    elsif using_open_id?
+      # 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]
+
       @title = t 'user.terms.title'
       @user = User.new(params[:user]) if params[:user]
 
+      if params[:user] and 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
         if @user.invalid?
           if @user.new_record?
+            # Something is wrong with a new user, so rerender the form
             render :action => :new
           else
+            # Error in existing user, so go to account settings
             flash[:errors] = @user.errors
             redirect_to :action => :account, :display_name => @user.display_name
           end
         elsif @user.terms_agreed?
+          # Already agreed to terms, so just show settings
           redirect_to :action => :account, :display_name => @user.display_name
+        elsif params[:user] and params[:user][:openid_url]
+          # Verify OpenID before moving on
+          session[:new_user] = @user
+          openid_verify(params[:user][:openid_url], @user)
         end
       else
+        # Not logged in, so redirect to the login page
         redirect_to :action => :login, :referer => request.request_uri
       end
     end
@@ -97,7 +128,7 @@ class UserController < ApplicationController
       
       if @user.save
         flash[:notice] = t 'user.new.flash create success message', :email => @user.email
-        Notifier.deliver_signup_confirm(@user, @user.tokens.create(:referer => params[:referer]))
+        Notifier.deliver_signup_confirm(@user, @user.tokens.create(:referer => session.delete(:referer)))
         session[:token] = @user.tokens.create.token
         redirect_to :action => 'login', :referer => params[:referer]
       else
@@ -136,22 +167,25 @@ class UserController < ApplicationController
         @user.preferred_editor = params[:user][:preferred_editor]
       end
 
-      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
+      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
+    elsif using_open_id?
+      # 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
     else
       if flash[:errors]
@@ -217,46 +251,26 @@ 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[:username] or using_open_id?
+      session[:remember_me] ||= params[:remember_me]
+      session[:referer] ||= params[:referer]
 
-    if params[:user]
-      email_or_display_name = params[:user][:email]
-      pass = params[:user][:password]
-      user = User.authenticate(:username => email_or_display_name, :password => pass)
-
-      if user
-        session[:user] = user.id
-        session_expires_after 1.month if params[:remember_me]
-
-        target = params[:referer] || url_for(:controller => :site, :action => :index)
-
-        # The user is logged in, so decide where to send them:
-        #
-        # - If they haven't seen the contributor terms, send them there.
-        # - If they have a block on them, show them that.
-        # - If they were referred to the login, send them back there.
-        # - Otherwise, send them to the home page.
-        if REQUIRE_TERMS_SEEN and not user.terms_seen
-          redirect_to :controller => :user, :action => :terms, :referer => target
-        elsif user.blocked_on_view
-          redirect_to user.blocked_on_view, :referer => target
-        else
-          redirect_to target
-        end
-      elsif user = User.authenticate(:username => email_or_display_name, :password => pass, :pending => true)
-        flash.now[:error] = t 'user.login.account not active', :reconfirm => url_for(:action => 'confirm_resend', :display_name => user.display_name)
-      elsif User.authenticate(:username => email_or_display_name, :password => pass, :suspended => true)
-        webmaster = link_to t('user.login.webmaster'), "mailto:webmaster@openstreetmap.org"
-        flash.now[:error] = t 'user.login.account suspended', :webmaster => webmaster
+      if using_open_id?
+        openid_authentication(params[:openid_url])
       else
-        flash.now[:error] = t 'user.login.auth failure'
+        password_authentication(params[:username], params[:password])
       end
     elsif flash[:notice].nil?
       flash.now[:notice] =  t 'user.login.notice'
@@ -476,6 +490,171 @@ 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 = User.authenticate(:username => username, :password => password, :pending => true)
+      failed_login t('user.login.account not active', :reconfirm => url_for(:action => 'confirm_resend', :display_name => user.display_name))
+    elsif User.authenticate(:username => username, :password => password, :suspended => true)
+      webmaster = link_to t('user.login.webmaster'), "mailto:webmaster@openstreetmap.org"
+      failed_login t('user.login.account suspended', :webmaster => webmaster)
+    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)
+      required = nil
+    else
+      required = [:nickname, :email, "http://axschema.org/namePerson/friendly", "http://axschema.org/contact/email"]
+    end
+
+    # Start the authentication
+    authenticate_with_open_id(openid_expand_url(openid_url), :required => required) do |result, identity_url, sreg, ax|
+      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
+              webmaster = link_to t('user.login.webmaster'), "mailto:webmaster@openstreetmap.org"
+              failed_login t('user.login.account suspended', :webmaster => webmaster)
+            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.
+          nickname = sreg["nickname"] || ax["http://axschema.org/namePerson/friendly"]
+          email = sreg["email"] || ax["http://axschema.org/contact/email"]
+          redirect_to :controller => 'user', :action => 'new', :nickname => nickname, :email => email, :openid => identity_url
+        end
+      elsif result.missing?
+        failed_login t('user.login.openid missing provider')
+      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_expand_url(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?
+        flash.now[:error] = t 'user.login.openid missing provider'
+      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 the correct URL based on what the user entered
+  def openid_expand_url(openid_url)
+    if openid_url.nil?
+      return nil
+    elsif openid_url.match(/(.*)gmail.com(\/?)$/) or openid_url.match(/(.*)googlemail.com(\/?)$/)
+      # 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.
+      return 'https://www.google.com/accounts/o8/id'
+    else
+      return openid_url
+    end
+  end  
+
+  ##
+  # process a successful login
+  def successful_login(user)
+    session[:user] = user.id
+    session_expires_after 1.month if session[:remember_me]
+
+    target = session[:referer] || url_for(:controller => :site, :action => :index)
+
+    # The user is logged in, so decide where to send them:
+    #
+    # - If they haven't seen the contributor terms, send them there.
+    # - If they have a block on them, show them that.
+    # - If they were referred to the login, send them back there.
+    # - Otherwise, send them to the home page.
+    if REQUIRE_TERMS_SEEN and not user.terms_seen
+      redirect_to :controller => :user, :action => :terms, :referer => target
+    elsif user.blocked_on_view
+      redirect_to user.blocked_on_view, :referer => target
+    else
+      redirect_to target
+    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 a817c0fcf404c5c518bd0f0fe613ca1fdde6e10d..0b2a902dfac734f71d18c183cefe5863e579bbe1 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, :if => Proc.new { |u| u.email_changed? }
index abece879f7622bb2362c36372b69f17acf5cfe73..16f3ad7d39ed00b2e559d0864514637bc202a081 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 8fe094cfa7b404075cab725a0309d5bd9acdf0e8..6297b219db58512a29f0973f6def39954b54cf1f 100644 (file)
 <div id="login_wrapper">
+
   <div id="login_login">
     <h1><%= t 'user.login.heading' %></h1>
 
-    <p><%= t 'user.login.already have' %></p>
-
-    <% form_tag :action => 'login' do %>
+    <% form_tag({ :action => "login" }, { :id => "login_form" }) do %>
       <%= hidden_field_tag('referer', h(params[:referer])) %>
+
+      <p><%= t 'user.login.with username' %></p>
+
       <table id="loginForm">
         <tr><td class="fieldName"><%= t 'user.login.email or username' %></td><td><%= text_field('user', 'email',{:value => params[:username], :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>
       </table>
       <%= submit_tag t('user.login.login_button'), :tabindex => 3 %>
+
+      <br clear="all" />
+
+      <p><%= t 'user.login.with openid' %></p>
+
+      <table id="login_openid_buttons_wide">
+        <tr>
+          <td>
+            <%=
+              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[:login_openid_buttons_wide].hide
+                page[:login_openid_url].show
+                page[:login_openid_submit].show
+              end
+            %>
+          </td>
+          <td><%= openid_button "google", "gmail.com" %></td>
+          <td><%= openid_button "yahoo", "me.yahoo.com" %></td>
+        </tr>
+        <tr>
+          <td><%= openid_button "myopenid", "myopenid.com" %></td>
+          <td><%= openid_button "wordpress", "wordpress.com" %></td>
+          <td><%= openid_button "aol", "aol.com" %></td>
+        </tr>
+      </table>
+
+      <table id="login_openid_buttons_narrow">
+        <tr>
+          <td>
+            <%=
+              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[:login_openid_buttons_narrow].hide
+                page[:login_openid_url].show
+                page[:login_openid_submit].show
+              end
+            %>
+          </td>
+          <td><%= openid_button "google", "gmail.com" %></td>
+        </tr>
+        <tr>
+          <td><%= openid_button "yahoo", "me.yahoo.com" %></td>
+          <td><%= openid_button "myopenid", "myopenid.com" %></td>
+        </tr>
+        <tr>
+          <td><%= openid_button "wordpress", "wordpress.com" %></td>
+          <td><%= openid_button "aol", "aol.com" %></td>
+        </tr>
+      </table>
+
+      <table>
+        <tr id="login_openid_url">
+          <td class="fieldName nowrap">
+            <%= t 'user.login.openid', :logo => openid_logo %>
+          </td>
+          <td>
+            <%= text_field_tag("openid_url", "", { :size => 28, :maxlength => 255, :tabindex => 3, :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 nowrap" id="remember_me_label"><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 => "login_openid_submit" %>
     <% end %>
+
     <br clear="all" />
   </div>
+
   <div id="login_signup">
     <h2><%= t 'user.login.new to osm' %></h2>
     <p><%= t 'user.login.to make changes' %></p>
     <p><%= t 'user.login.create account minute' %></p>
     <p><%= button_to t('user.login.register now'), :action => :new, :referer => params[:referer] %></p>
-    <br clear="all" />
+
+    <br clear="both">
   </div>
+
 </div>
+
+<%=
+  update_page_tag do |page|
+    page[:login_openid_url].hide
+    page[:login_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 cd352ab6fde9af90e80c5b9f68e17ae6535fc14f..e4644e8151758a6180acc98d07ced0db70d0863f 100644 (file)
   </p>
   <p>
     <%= hidden_field_tag('referer', h(params[:referer])) unless params[:referer].nil? %>
-    <% if params[:user] %>
+    <% if @user.new_record? %>
       <%= hidden_field('user', 'email') %>
       <%= hidden_field('user', 'email_confirmation') %>
       <%= hidden_field('user', 'display_name') %>
       <%= hidden_field('user', 'pass_crypt') %>
       <%= hidden_field('user', 'pass_crypt_confirmation') %>
+      <%= hidden_field('user', 'openid_url') %>
     <% end %>
     <div id="buttons">
       <%= submit_tag(t('user.terms.decline'), :name => "decline", :id => "decline") %>
diff --git a/config/initializers/openid.rb b/config/initializers/openid.rb
new file mode 100644 (file)
index 0000000..966164b
--- /dev/null
@@ -0,0 +1 @@
+OpenIdAuthentication.store = :file
index 48eb9ce1269f239f53243c3602b51bda88daf84e..e41048d0fb0259da4112c6ad6601dc8abbc5a078 100644 (file)
@@ -1519,15 +1519,15 @@ en:
     login:
       title: "Login"
       heading: "Login"
-      please login: "Please login or %{create_user_link}."
-      create_account: "create an account"
       email or username: "Email Address or Username:"
       password: "Password:"
+      openid: "%{logo} OpenID:"
       remember: "Remember me:"
       lost password link: "Lost your password?"
       login_button: "Login"
       register now: Register now
-      already have: Already have an OpenStreetMap account? Please login.
+      with username: "Already have an OpenStreetMap account? Please login with your username and password:"
+      with openid: "Alternatively please use your OpenID to login:"
       new to osm: New to OpenStreetMap?
       to make changes: To make changes to the OpenStreetMap data, you must have an account.
       create account minute: Create an account. It only takes a minute.
@@ -1536,6 +1536,28 @@ en:
       webmaster: webmaster
       auth failure: "Sorry, could not log in with those details."
       notice: "<a href=\"http://www.osmfoundation.org/wiki/License/We_Are_Changing_The_License\">Find out more about OpenStreetMap's upcoming license change</a> (<a href=\"http://wiki.openstreetmap.org/wiki/ODbL/We_Are_Changing_The_License\">translations</a>) (<a href=\"http://wiki.openstreetmap.org/wiki/Talk:ODbL/Upcoming\">discussion</a>)"
+      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 OpenID
+          alt: Login with an OpenID URL
+        google:
+          title: Login with Google
+          alt: Login with a Google OpenID
+        yahoo:
+          title: Login with Yahoo
+          alt: Login with a Yahoo OpenID
+        myopenid:
+          title: Login with myOpenID
+          alt: Login with a myOpenID OpenID
+        wordpress:
+          title: Login with Wordpress
+          alt: Login with a Wordpress OpenID
+        aol:
+          title: Login with AOL
+          alt: Login with an AOL OpenID
     logout:
       title: "Logout"
       heading: "Logout from OpenStreetMap"
@@ -1568,8 +1590,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: "Thanks for signing up. We've sent a confirmation note to %{email} and as soon as you confirm your account you'll be able to get mapping.<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 accepted: "Thanks for accepting the new contributor terms!"
@@ -1657,6 +1692,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 66a78861dde60cb20dbf03d9d8269fe820e35fe6..dfdacf9e9c95e182d588f905f2b4334ecc185bcc 100644 (file)
@@ -1011,6 +1011,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:"
@@ -1070,6 +1072,28 @@ is:
       remember: "Muna innskráninguna:"
       title: Innskrá
       webmaster: vefstjóra
+      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á
@@ -1101,6 +1125,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/20100516124737_add_open_id.rb b/db/migrate/20100516124737_add_open_id.rb
new file mode 100644 (file)
index 0000000..2fb9ee5
--- /dev/null
@@ -0,0 +1,11 @@
+class AddOpenId < ActiveRecord::Migration
+  def self.up
+    add_column :users, :openid_url, :string 
+    add_index :users, [:openid_url], :name => "user_openid_url_idx", :unique => true
+  end
+
+  def self.down
+    remove_index :users, :name => "user_openid_url_idx"
+    remove_column :users, :openid_url
+  end
+end
diff --git a/public/images/aol.png b/public/images/aol.png
new file mode 100644 (file)
index 0000000..7b2f228
Binary files /dev/null and b/public/images/aol.png differ
diff --git a/public/images/google.png b/public/images/google.png
new file mode 100644 (file)
index 0000000..ea2e380
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..1d9632c
Binary files /dev/null and b/public/images/myopenid.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..08875c4
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..57cdd1d
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..e7b148d
Binary files /dev/null and b/public/images/yahoo.png differ
index 8b387e1d34dbc5b1555b24d5a28836e36e94b7a5..89fa39b31f59358d24247b552deaf27545f564c8 100644 (file)
@@ -723,6 +723,21 @@ table.browse_details th {
   margin-top: 5px;
 }
 
+table#login_openid_buttons {
+  padding-bottom: 10px;
+}
+
+#login_openid_buttons td {
+  padding-left: 10px;
+  padding-right: 10px;
+  padding-top: 5px;
+  padding-bottom: 5px;
+}
+
+#login_openid_buttons img {
+  border: 0;
+}
+
 #login_signup form.button-to div {
   margin: 0px;
   padding: 0px;
@@ -927,6 +942,11 @@ input[type="submit"] {
   border: 1px solid black;
 }
 
+input.openid_url { 
+  background: url('../images/openid_input.png') repeat-y left white;
+  padding-left: 16px;
+}
+
 /* Rules for user images */
 
 img.user_image {
@@ -969,3 +989,10 @@ abbr.geo {
 .table1 { 
   background: #fff;
 }
+
+/* Rules for OpenID logo */
+
+.openid_logo {
+  vertical-align: text-bottom;
+  border: 0;
+}
index a1efa8583621c7b894955d708648f3cf3e2be61b..95490fddc657dea617883416898617bf5ecb3e13 100644 (file)
@@ -18,3 +18,9 @@
 .olControlPanZoom {
   display: none;
 }
+
+/* Rules for the login form */
+
+#login_openid_buttons_narrow {
+  display: none;
+}
index f4d51c868453d43c9e9edc781b262f8d9ab908d0..2c6f669df021b4d60ea988a58ae1e34f831547c1 100644 (file)
@@ -136,6 +136,10 @@ h1 {
   max-width: 18em;
 }
 
+#login_openid_buttons_wide {
+  display: none;
+}
+
 /* Rules for the profile page */
 
 .user_map {
index 98517fd2aa86f9b24134f0ce560027349c93324a..ba66d87953e29c3349a903737d3d73c75af5c7eb 100644 (file)
@@ -84,3 +84,14 @@ terms_not_seen_user:
   display_name: not_agreed
   data_public: true
   terms_seen: false
+
+openid_user:
+  id: 8
+  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
+  terms_seen: 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 b4ca49022ec056c8b6fbccb6b283e5a4082b3640..7003d769251c2a97dbe0578a35bf627e60255c41 100644 (file)
@@ -39,7 +39,7 @@ class UserBlocksTest < ActionController::IntegrationTest
     # revoke the ban
     get '/login'
     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 01a7ca649dab7c2d8669d20aa8d722b1382fb602..9cb5e895ccf4993f672f036d6da0352261ed6d4b 100644 (file)
@@ -3,6 +3,10 @@ require File.dirname(__FILE__) + '/../test_helper'
 class UserCreationTest < ActionController::IntegrationTest
   fixtures :users
 
+  def setup
+    openid_setup
+  end
+
   def test_create_user_form
     I18n.available_locales.each do |locale|
       get '/user/new', {}, {"accept_language" => locale.to_s}
@@ -94,8 +98,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 +135,99 @@ 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
+
+  def test_user_create_openid_failure
+    new_email = "newtester-openid2@osm.org"
+    display_name = "new_tester-openid2"
+    password = "testtest2"
+    assert_difference('User.count',0) do
+      assert_difference('ActionMailer::Base.deliveries.size',0) do
+        post "/user/terms",
+          {:user => { :email => new_email, :email_confirmation => new_email, :display_name => display_name, :openid_url => "http://localhost:1123/john.doe?openid.failure=newuser", :pass_crypt => "", :pass_crypt_confirmation => ""}}
+        assert_response :redirect
+        res = openid_request(@response.redirected_to)
+        post '/user/terms', res
+        assert_response :success
+        assert_template 'user/new'
+      end
+    end
+
+    ActionMailer::Base.deliveries.clear
+  end
+
+  def test_user_create_openid_redirect
+    new_email = "redirect_tester_openid@osm.org"
+    display_name = "redirect_tester_openid"
+    password = ""
+    # nothing special about this page, just need a protected page to redirect back to.
+    referer = "/traces/mine"
+    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 => ""}, :referer => referer }
+       assert_response :redirect
+        res = openid_request(@response.location)
+        post '/user/terms', res
+        assert_response :success
+        assert_template 'terms'
+        post_via_redirect "/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 => "testtest", :pass_crypt_confirmation => "testtest"} }
+      end
+    end
+
+    # Check the e-mail
+    register_email = ActionMailer::Base.deliveries.first
+
+    assert_equal register_email.to[0], new_email
+    # Check that the confirm account url is correct
+    confirm_regex = Regexp.new("/user/redirect_tester_openid/confirm\\?confirm_string=([a-zA-Z0-9]*)")
+    assert_match(confirm_regex, register_email.body)
+    confirm_string = confirm_regex.match(register_email.body)[1]
+
+    # Check the page
+    assert_response :success
+    assert_template 'login'
+
+    ActionMailer::Base.deliveries.clear
+
+    # Go to the confirmation page
+    get 'user/confirm', { :confirm_string => confirm_string }
+    assert_response :success
+    assert_template 'user/confirm'
+
+    post 'user/confirm', { :confirm_string => confirm_string, :confirm_action => 'submit' }
+    assert_response :redirect # to trace/mine in original referrer
+    follow_redirect!
+    assert_response :redirect # but it not redirects to /user/<display_name>/traces
+    follow_redirect!
+    assert_response :success
+    assert_template "trace/list.html.erb"
+  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..f76f3ca
--- /dev/null
@@ -0,0 +1,91 @@
+require File.dirname(__FILE__) + '/../test_helper'
+
+class UserLoginTest < ActionController::IntegrationTest
+  fixtures :users
+
+  def setup
+    openid_setup
+  end
+
+  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 6a618c8d3e6073bf24e1f2c7bbf77fd61269a4ba..dc29287f51a0f399f7aeb4263d86391d3cae4f50 100644 (file)
@@ -3,6 +3,43 @@ require File.expand_path(File.dirname(__FILE__) + "/../config/environment")
 require 'test_help'
 load 'composite_primary_keys/fixtures.rb'
 
+# This monkey patch is to make tests where a rack module alters
+# the response work with rails 2 - it can be dropped when we move
+# to rails 3.
+module ActionController
+  module Integration
+    class Session
+      def process_with_capture(method, path, parameters = nil, headers = nil)
+        status = process_without_capture(method, path, parameters, headers)
+        @controller = ActionController::Base.last_controller
+        @request = @controller.request
+        @response.session = @controller.response.session
+        @response.template = @controller.response.template
+        @response.redirected_to = @response.location
+        status
+      end
+
+      alias_method_chain :process, :capture
+    end
+
+    module ControllerCapture
+      module ClassMethods
+        mattr_accessor :last_controller
+
+        def clear_last_instantiation!
+          self.last_controller = nil
+        end
+
+        def new_with_capture(*args)
+          controller = new_without_capture(*args)
+          self.last_controller ||= controller
+          controller
+        end
+      end
+    end
+  end
+end
+
 class ActiveSupport::TestCase
   # Transactional fixtures accelerate your tests by wrapping each test method
   # in a transaction that's rolled back on completion.  This ensures that the
@@ -145,6 +182,44 @@ class ActiveSupport::TestCase
   def assert_no_missing_translations(msg="")
     assert_select "span[class=translation_missing]", false, "Missing translation #{msg}"
   end
+
+  # Set things up for OpenID testing
+  def openid_setup
+    begin
+      # Test if the ROTS (Ruby OpenID Test Server) is already running
+      rots_response = Net::HTTP.get_response(URI.parse("http://localhost:1123/"))
+    rescue
+      # It isn't, so start a new instance.
+      rots = IO.popen(RAILS_ROOT + "/vendor/gems/rots-0.2.1/bin/rots --silent")
+
+      # Wait for up to 30 seconds for the server to start and respond before continuing
+      for i in (1 .. 30)
+       begin
+         sleep 1
+         rots_response = Net::HTTP.get_response(URI.parse("http://localhost:1123/"))
+         # If the rescue block doesn't fire, ROTS is up and running and we can continue
+         break
+       rescue
+         # If the connection failed, do nothing and repeat the loop
+       end
+      end
+
+      # Arrange to kill the process when we exit - note that we need
+      # to kill it really har due to a bug in ROTS
+      Kernel.at_exit do
+        Process.kill("KILL", rots.pid)
+      end
+    end
+  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/.specification b/vendor/gems/rots-0.2.1/.specification
new file mode 100644 (file)
index 0000000..68c7f20
--- /dev/null
@@ -0,0 +1,112 @@
+--- !ruby/object:Gem::Specification 
+name: rots
+version: !ruby/object:Gem::Version 
+  prerelease: false
+  segments: 
+  - 0
+  - 2
+  - 1
+  version: 0.2.1
+platform: ruby
+authors: 
+- Roman Gonzalez
+autorequire: 
+bindir: bin
+cert_chain: []
+
+date: 2010-05-11 00:00:00 +01:00
+default_executable: rots
+dependencies: 
+- !ruby/object:Gem::Dependency 
+  name: rspec
+  prerelease: false
+  requirement: &id001 !ruby/object:Gem::Requirement 
+    requirements: 
+    - - ">="
+      - !ruby/object:Gem::Version 
+        segments: 
+        - 0
+        version: "0"
+  type: :development
+  version_requirements: *id001
+- !ruby/object:Gem::Dependency 
+  name: rack
+  prerelease: false
+  requirement: &id002 !ruby/object:Gem::Requirement 
+    requirements: 
+    - - ">="
+      - !ruby/object:Gem::Version 
+        segments: 
+        - 0
+        version: "0"
+  type: :development
+  version_requirements: *id002
+- !ruby/object:Gem::Dependency 
+  name: ruby-openid
+  prerelease: false
+  requirement: &id003 !ruby/object:Gem::Requirement 
+    requirements: 
+    - - ">="
+      - !ruby/object:Gem::Version 
+        segments: 
+        - 0
+        version: "0"
+  type: :development
+  version_requirements: *id003
+description: |
+  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.
+
+email: romanandreg@gmail.com
+executables: 
+- rots
+extensions: []
+
+extra_rdoc_files: 
+- README
+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
+has_rdoc: true
+homepage: http://github.com/roman
+licenses: []
+
+post_install_message: 
+rdoc_options: []
+
+require_paths: 
+- lib
+required_ruby_version: !ruby/object:Gem::Requirement 
+  requirements: 
+  - - ">="
+    - !ruby/object:Gem::Version 
+      segments: 
+      - 0
+      version: "0"
+required_rubygems_version: !ruby/object:Gem::Requirement 
+  requirements: 
+  - - ">="
+    - !ruby/object:Gem::Version 
+      segments: 
+      - 0
+      version: "0"
+requirements: []
+
+rubyforge_project: rots
+rubygems_version: 1.3.6
+signing_key: 
+specification_version: 3
+summary: an OpenID server for making tests of OpenID clients implementations
+test_files: 
+- spec/server_app_spec.rb
+- spec/spec_helper.rb
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..27178d8
--- /dev/null
@@ -0,0 +1,99 @@
+#!/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
+  if server_options[:verbose]
+    Rack::Handler::WEBrick.run server, :Port => server_options[:port]
+  else
+    Rack::Handler::WEBrick.run server, :Port => server_options[:port], :AccessLog => [], :Logger => WEBrick::Log::new("/dev/null", 7)
+  end
+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..0c8971e
--- /dev/null
@@ -0,0 +1,37 @@
+* Dump heavy lifting off to rack-openid gem. OpenIdAuthentication is just a simple controller concern.
+
+* 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..fe2b37e
--- /dev/null
@@ -0,0 +1,223 @@
+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.
+
+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/init.rb b/vendor/plugins/open_id_authentication/init.rb
new file mode 100644 (file)
index 0000000..84ec11f
--- /dev/null
@@ -0,0 +1,12 @@
+if Rails.version < '3'
+  config.gem 'rack-openid', :lib => 'rack/openid', :version => '>=0.2.1'
+end
+
+require 'open_id_authentication'
+
+config.middleware.use OpenIdAuthentication
+
+config.after_initialize 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..ed875cd
--- /dev/null
@@ -0,0 +1,129 @@
+require 'uri'
+require 'openid'
+require 'rack/openid'
+
+module OpenIdAuthentication
+  def self.new(app)
+    store = OpenIdAuthentication.store
+    if store.nil?
+      Rails.logger.warn "OpenIdAuthentication.store is nil. Using in-memory store."
+    end
+
+    ::Rack::OpenID.new(app, OpenIdAuthentication.store)
+  end
+
+  def self.store
+    @@store
+  end
+
+  def self.store=(*store_option)
+    store, *parameters = *([ store_option ].flatten)
+
+    @@store = case store
+    when :memory
+      require 'openid/store/memory'
+      OpenID::Store::Memory.new
+    when :file
+      require 'openid/store/filesystem'
+      OpenID::Store::Filesystem.new(Rails.root.join('tmp/openids'))
+    when :memcache
+      require 'memcache'
+      require 'openid/store/memcache'
+      OpenID::Store::Memcache.new(MemCache.new(parameters))
+    else
+      store
+    end
+  end
+
+  self.store = nil
+
+  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
+
+  protected
+    # 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?(identifier = nil) #:doc:
+      identifier ||= open_id_identifier
+      !identifier.blank? || request.env[Rack::OpenID::RESPONSE]
+    end
+
+    def authenticate_with_open_id(identifier = nil, options = {}, &block) #:doc:
+      identifier ||= open_id_identifier
+
+      if request.env[Rack::OpenID::RESPONSE]
+        complete_open_id_authentication(&block)
+      else
+        begin_open_id_authentication(identifier, options, &block)
+      end
+    end
+
+  private
+    def open_id_identifier
+      params[:openid_identifier] || params[:openid_url]
+    end
+
+    def begin_open_id_authentication(identifier, options = {})
+      options[:identifier] = identifier
+      value = Rack::OpenID.build_header(options)
+      response.headers[Rack::OpenID::AUTHENTICATE_HEADER] = value
+      head :unauthorized
+    end
+
+    def complete_open_id_authentication
+      response   = request.env[Rack::OpenID::RESPONSE]
+      identifier = response.display_identifier
+
+      case response.status
+      when OpenID::Consumer::SUCCESS
+        yield Result[:successful], identifier,
+          OpenID::SReg::Response.from_success_response(response),
+          OpenID::AX::FetchResponse.from_success_response(response)
+      when :missing
+        yield Result[:missing], identifier, nil
+      when :invalid
+        yield Result[:invalid], identifier, nil
+      when OpenID::Consumer::CANCEL
+        yield Result[:canceled], identifier, nil
+      when OpenID::Consumer::FAILURE
+        yield Result[:failed], identifier, nil
+      when OpenID::Consumer::SETUP_NEEDED
+        yield Result[:setup_needed], response.setup_url, nil
+      end
+    end
+end