]> git.openstreetmap.org Git - rails.git/commitdiff
Merge branch 'master' into openid
authorTom Hughes <tom@compton.nu>
Mon, 19 Apr 2010 23:41:03 +0000 (00:41 +0100)
committerTom Hughes <tom@compton.nu>
Mon, 19 Apr 2010 23:41:03 +0000 (00:41 +0100)
Conflicts:
app/controllers/user_controller.rb
app/views/user/login.html.erb
config/locales/en.yml

1  2 
app/controllers/user_controller.rb
app/models/user.rb
app/views/user/account.html.erb
app/views/user/login.html.erb
app/views/user/new.html.erb
config/environment.rb
config/locales/en.yml

index 97e184b5943cef9269668e13520893f6867b9375,9551ac6d8fbdd1e21524a6d1f7065f079c7618b9..428a8b90c8e998e5b175568244a051ea3f04b5a3
@@@ -24,18 -24,6 +24,18 @@@ class UserController < ApplicationContr
      if Acl.find_by_address(request.remote_ip, :conditions => {:k => "no_account_creation"})
        render :action => 'new'
      else
 +        #The redirect from the OpenID provider reenters here again 
 +      #and we need to pass the parameters through to the  
 +      #open_id_authentication function a second time 
 +      if params[:open_id_complete] 
 +        openid_verify('', true) 
 +        #We have set the user.openid_url to nil beforehand. If it hasn't 
 +        #been set to a new valid openid_url, it means the openid couldn't be validated 
 +        if @user.nil? or @user.openid_url.nil? 
 +          render :action => 'new' 
 +          return 
 +        end   
 +      else
        @user = User.new(params[:user])
  
        @user.visible = true
        @user.description = "" if @user.description.nil?
        @user.creation_ip = request.remote_ip
        @user.languages = request.user_preferred_languages
 +        #Set the openid_url to nil as for one it is used 
 +        #to check if the openid could be validated and secondly 
 +        #to not get dupplicate conflicts for an empty openid  
 +        @user.openid_url = nil
 +
 +if (!params[:user][:openid_url].nil? and params[:user][:openid_url].length > 0)
 +                if @user.pass_crypt.length == 0 
 +            #if the password is empty, but we have a openid 
 +            #then generate a random passowrd to disable 
 +            #loging in via password 
 +            @user.pass_crypt = ActiveSupport::SecureRandom.base64(16) 
 +            @user.pass_crypt_confirmation = @user.pass_crypt 
 +          end
 +                #Validate all of the other fields before
 +                #redirecting to the openid provider
 +                if !@user.valid?
 +                      render :action => 'new'
 +                else            
 +                      #TODO: Is it a problem to store the user variable with respect to password safty in the session variables?
 +                      #Store the user variable in the session for it to be accessible when redirecting back from the openid provider
 +                      session[:new_usr] = @user
 +                      begin
 +                        @norm_openid_url = OpenIdAuthentication.normalize_identifier(params[:user][:openid_url])
 +                      rescue
 +                        flash.now[:error] = t 'user.login.openid invalid'
 +                        render :action => 'new'
 +                        return
 +                      end
 +                      #Verify that the openid provided is valid and that the user is the owner of the id
 +                      openid_verify(@norm_openid_url, true)
 +                      #openid_verify can return in two ways:
 +                      #Either it returns with a redirect to the openid provider who then freshly
 +                      #redirects back to this url if the openid is valid, or if the openid is not plausible
 +                      #and no provider for it could be found it just returns
 +                      #we want to just let the redirect through
 +                      if response.headers["Location"].nil?
 +                        render :action => 'new'
 +                      end
 +                end
 +                #At this point there was either an error and the page has been rendered,
 +                #or there is a redirect to the openid provider and the rest of the method
 +                #gets executed whenn this method gets reentered after redirecting back
 +                #from the openid provider
 +                return
 +              end
 +        end
  
        if @user.save
          flash[:notice] = t 'user.new.flash create success message'
      @title = t 'user.account.title'
      @tokens = @user.oauth_tokens.find :all, :conditions => 'oauth_tokens.invalidated_at is null and oauth_tokens.authorized_at is not null'
  
 +      #The redirect from the OpenID provider reenters here again
 +    #and we need to pass the parameters through to the 
 +    #open_id_authentication function
 +    if params[:open_id_complete]
 +      openid_verify('', false)
 +        @user.save
 +      return
 +    end
 +
      if params[:user] and params[:user][:display_name] and params[:user][:description]
        @user.display_name = params[:user][:display_name]
        @user.new_email = params[:user][:new_email]
            end
          end
        end
 +
 +        if (params[:user][:openid_url].length > 0)
 +              begin
 +                @norm_openid_url = OpenIdAuthentication.normalize_identifier(params[:user][:openid_url])
 +                if (@norm_openid_url != @user.openid_url)
 +                      #If the OpenID has changed, we want to check that it is a valid OpenID and one
 +                      #the user has control over before saving the openID as a password equivalent for
 +                      #the user.
 +                      openid_verify(@norm_openid_url, false)
 +                end
 +              rescue
 +                flash.now[:error] = t 'user.login.openid invalid'
 +              end
 +      end
 +
      else
        if flash[:errors]
          flash[:errors].each do |attr,msg|
      end
    end
  
 +  def openid_specialcase_mapping(openid_url)
 +    #Special case gmail.com, as it is pontentially a popular OpenID provider and unlike
 +    #yahoo.com, where it works automatically, Google have hidden their OpenID endpoint
 +    #somewhere obscure making it less userfriendly.
 +    if (openid_url.match(/(.*)gmail.com(\/?)$/) or openid_url.match(/(.*)googlemail.com(\/?)$/) )
 +      return 'https://www.google.com/accounts/o8/id'
 +    end
 +
 +    return nil
 +  end  
 +
 +  def openid_verify(openid_url,account_create)
 +    authenticate_with_open_id(openid_url) do |result, identity_url|
 +      if result.successful?
 +        #We need to use the openid url passed back from the OpenID provider
 +        #rather than the one supplied by the user, as these can be different.
 +        #e.g. one can simply enter yahoo.com in the login box, i.e. no user specific url
 +        #only once it comes back from the OpenID provider do we know the unique address for
 +        #the user.
 +              @user = session[:new_usr] unless @user #this is used for account creation when the user is not yet in the database
 +        @user.openid_url = identity_url
 +        elsif result.missing?
 +              mapped_id = openid_specialcase_mapping(openid_url)
 +              if mapped_id
 +                openid_verify(mapped_id, account_create)
 +              else
 +                flash.now[:error] = t 'user.login.openid missing provider'
 +              end
 +        elsif result.invalid?
 +              flash.now[:error] = t 'user.login.openid invalid'
 +        else
 +              flash.now[:error] = t 'user.login.auth failure'
 +        end
 +    end
 +  end
 +
 +  def open_id_authentication(openid_url)
 +    #TODO: only ask for nickname and email, if we don't already have a user for that openID, in which case
 +    #email and nickname are already filled out. I don't know how to do that with ruby syntax though, as we
 +    #don't want to duplicate the do block
 +    #On the other hand it also doesn't matter too much if we ask every time, as the OpenID provider should
 +    #remember these results, and shouldn't repromt the user for these data each time.
 +    authenticate_with_open_id(openid_url, :return_to => request.protocol + request.host_with_port + '/login?referer=' + params[:referer], :optional => [:nickname, :email]) do |result, identity_url, registration|
 +      if result.successful?
 +        #We need to use the openid url passed back from the OpenID provider
 +        #rather than the one supplied by the user, as these can be different.
 +        #e.g. one can simply enter yahoo.com in the login box, i.e. no user specific url
 +        #only once it comes back from the OpenID provider do we know the unique address for
 +        #the user.
 +        user = User.find_by_openid_url(identity_url)
 +        if user
 +          if user.visible? and user.active?
 +            session[:user] = user.id
 +                      session_expires_after 1.month if session[:remember]
 +          else
 +            user = nil
 +            flash.now[:error] = t 'user.login.account not active'
 +          end
 +        else
 +          #We don't have a user registered to this OpenID. Redirect to the create account page
 +          #with username and email filled in if they have been given by the OpenID provider through
 +          #the simple registration protocol
 +          redirect_to :controller => 'user', :action => 'new', :nickname => registration['nickname'], :email => registration['email'], :openid => identity_url
 +        end
 +      else if result.missing?
 +             #Try and apply some heuristics to make common cases more userfriendly
 +             mapped_id = openid_specialcase_mapping(openid_url)
 +             if mapped_id
 +               open_id_authentication(mapped_id)
 +             else
 +               flash.now[:error] = t 'user.login.openid missing provider'
 +             end
 +           else if result.invalid?
 +                  flash.now[:error] = t 'user.login.openid invalid'
 +                else
 +                  flash.now[:error] = t 'user.login.auth failure'
 +                end
 +           end
 +      end
 +    end
++
++    user
 +  end
 +
    def go_public
      @user.data_public = true
      @user.save
    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
+     # The user is logged in already, so don't show them the signup
+     # page, instead send them to the home page
      redirect_to :controller => 'site', :action => 'index' if session[:user]
 +
 +      @nickname = params['nickname']
 +    @email = params['email']
 +      @openID = params['openid']
    end
  
    def login
+     @title = t 'user.login.title'
  
-       #The redirect from the OpenID provider reenters here again
 -    if params[:user]
 -      email_or_display_name = params[:user][:email]
 -      pass = params[:user][:password]
 -      user = User.authenticate(:username => email_or_display_name, :password => pass)
 -
 -      if user
 -        session[:user] = user.id
 -        session_expires_after 1.month if params[:remember_me]
 -
 -        # The user is logged in, if the referer param exists, redirect
 -        # them to that unless they've also got a block on them, in
 -        # which case redirect them to the block so they can clear it.
 -        if user.blocked_on_view
 -          redirect_to user.blocked_on_view, :referrer => params[:referrer]
 -        elsif params[:referer]
 -          redirect_to params[:referer]
++    #The redirect from the OpenID provider reenters here again
 +    #and we need to pass the parameters through to the 
 +    # open_id_authentication function
 +    if params[:open_id_complete]
-       open_id_authentication('')
-     end
-     if params[:user] and session[:user].nil?
-         if !params[:user][:openid_url].nil? and !params[:user][:openid_url].empty?
-               session[:remember] = params[:remember_me]
-         open_id_authentication(params[:user][:openid_url])
++      user = open_id_authentication('')
++    elsif params[:user]
++      if !params[:user][:openid_url].nil? and !params[:user][:openid_url].empty?
++        session[:remember] = params[:remember_me]
++        user = open_id_authentication(params[:user][:openid_url])
 +      else
-               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]
-               elsif User.authenticate(:username => email_or_display_name, :password => pass, :inactive => true)
-                 flash.now[:error] = t 'user.login.account not active'
-               else
-                 flash.now[:error] = t 'user.login.auth failure'
-               end
-         end
++        email_or_display_name = params[:user][:email]
++        pass = params[:user][:password]
++
++        if user = User.authenticate(:username => email_or_display_name, :password => pass)
++          session[:user] = user.id
++          session_expires_after 1.month if params[:remember_me]
++        elsif User.authenticate(:username => email_or_display_name, :password => pass, :inactive => true)
++          flash.now[:error] = t 'user.login.account not active'
+         else
 -          redirect_to :controller => 'site', :action => 'index'
++          flash.now[:error] = t 'user.login.auth failure'
+         end
 -       elsif User.authenticate(:username => email_or_display_name, :password => pass, :inactive => true)
 -        flash.now[:error] = t 'user.login.account not active'
++      end
 +    end
 +
-     if session[:user]
-       # The user is logged in, if the referer param exists, redirect them to that
-       # unless they've also got a block on them, in which case redirect them to
-       # the block so they can clear it.
-       user = User.find(session[:user])
-       block = user.blocked_on_view
-       if block
-         redirect_to block, :referrer => params[:referrer]
++    if user
++      # The user is logged in, if the referer param exists, redirect
++      # them to that unless they've also got a block on them, in
++      # which case redirect them to the block so they can clear it.
++      if user.blocked_on_view
++        redirect_to user.blocked_on_view, :referrer => params[:referrer]
 +      elsif params[:referer]
 +        redirect_to params[:referer]
        else
 -        flash.now[:error] = t 'user.login.auth failure'
 +        redirect_to :controller => 'site', :action => 'index'
        end
-       return
      end
-     @title = t 'user.login.title'
    end
  
    def logout
-     if session[:token]
-       token = UserToken.find_by_token(session[:token])
-       if token
-         token.destroy
+     @title = t 'user.logout.title'
+     if params[:session] == request.session_options[:id]
+       if session[:token]
+         token = UserToken.find_by_token(session[:token])
+         if token
+           token.destroy
+         end
+         session[:token] = nil
+       end
+       session[:user] = nil
+       session_expires_automatically
+       if params[:referer]
+         redirect_to params[:referer]
+       else
+         redirect_to :controller => 'site', :action => 'index'
        end
-       session[:token] = nil
-     end
-     session[:user] = nil
-     session_expires_automatically
-     if params[:referer]
-       redirect_to params[:referer]
-     else
-       redirect_to :controller => 'site', :action => 'index'
      end
    end
  
          flash[:warning] = t 'user.make_friend.already_a_friend', :name => name
        end
  
-       redirect_to :controller => 'user', :action => 'view'
+       if params[:referer]
+         redirect_to params[:referer]
+       else
+         redirect_to :controller => 'user', :action => 'view'
+       end
      end
    end
  
          flash[:error] = t 'user.remove_friend.not_a_friend', :name => friend.display_name
        end
  
-       redirect_to :controller => 'user', :action => 'view'
+       if params[:referer]
+         redirect_to params[:referer]
+       else
+         redirect_to :controller => 'user', :action => 'view'
+       end
      end
    end
  
diff --combined app/models/user.rb
index 1a476954440f8b308be08c63c0ed8072dd7c5e03,f02c9a5cd0067b63afc3984575534c38fb0a550b..09e1a7d35611a9c3178a4832a3b19845b5faf0fe
@@@ -22,7 -22,6 +22,7 @@@ class User < ActiveRecord::Bas
    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
@@@ -86,7 -85,7 +86,7 @@@
    end
  
    def languages
-     attribute_present?(:languages) ? read_attribute(:languages).split(",") : []
+     attribute_present?(:languages) ? read_attribute(:languages).split(/ *, */) : []
    end
  
    def languages=(languages)
index c20f3526c69103aa9da24c5a0f525c1885c6ac91,85e9aebefb300142b3465176b81ee0f3b33c88ad..badcc1249c2ceb81610589b938ffd01ac719e61d
@@@ -6,6 -6,7 +6,6 @@@
      <td class="fieldName"><%= t 'user.new.display name' %></td>
      <td><%= f.text_field :display_name %></td>
    </tr>
 -
    <tr>
      <td class="fieldName" style="padding-bottom:0px;"><%= t 'user.account.current email address' %></td>
      <td style="padding-bottom:0px;"><%= @user.email %> <span class="minorNote"><%= t 'user.account.email never displayed publicly' %></span></td>
  
    <tr>
      <td class="fieldName" style="padding-bottom:0px;"><%= t 'user.new.password' %></td>
-     <td style="padding-bottom:0px;"><%= f.password_field :pass_crypt, {:value => '', :size => 30, :maxlength => 255} %></td>
+     <td style="padding-bottom:0px;"><%= f.password_field :pass_crypt, {:value => '', :size => 30, :maxlength => 255, :autocomplete => :off} %></td>
    </tr>
  
    <tr>
      <td class="fieldName"><%= t 'user.new.confirm password' %></td>
-     <td><%= f.password_field :pass_crypt_confirmation, {:value => '', :size => 30, :maxlength => 255} %></td>
+     <td><%= f.password_field :pass_crypt_confirmation, {:value => '', :size => 30, :maxlength => 255, :autocomplete => :off} %></td>
    </tr>
 +  <tr>
 +    <td class="fieldName" ><%= t 'user.account.openid.openid' %></td>
 +    <td style="padding-bottom:0px;"><%= f.text_field :openid_url %> (<a href="<%= t 'user.account.openid.link' %>" target="_new"><%= t 'user.account.openid.link text' %></a>) </td>
 +</tr>
  
    <tr>
      <td class="fieldName" valign="top"><%= t 'user.account.public editing.heading' %></td>
      <td valign="top">
        <% if @user.image.nil? %>
          <%= hidden_field_tag "image_action", "new" %>
-         <%= t 'user.account.new image' %><br /><%= file_column_field "user", "image" %>
+         <%= t 'user.account.new image' %><br /><%= file_column_field "user", "image" %><br /><span class="minorNote"><%= t 'user.account.image size hint' %></span>
        <% else %>
-         <table>
+         <table id="accountImage">
            <tr>
-             <td rowspan="3" valign="top"><%= image_tag url_for_file_column(@user, "image") %></td>
+             <td rowspan="3" valign="top"><%= image_tag url_for_file_column(@user, "image"), :class => "user_image" %></td>
              <td><%= radio_button_tag "image_action", "keep", true %></td>
              <td><%= t 'user.account.keep image' %></td>
            </tr>
@@@ -72,7 -69,7 +72,7 @@@
            </tr>
            <tr>
              <td><%= radio_button_tag "image_action", "new" %></td>
-             <td><%= t 'user.account.replace image' %><br /><%= file_column_field "user", "image", :onchange => "$('image_action_new').checked = true" %></td>
+             <td><%= t 'user.account.replace image' %><br /><%= file_column_field "user", "image", :onchange => "$('image_action_new').checked = true" %><br /><span class="minorNote"><%= t 'user.account.image size hint' %></span></td>
            </tr>
          </table>
        <% end %>
@@@ -88,7 -85,7 +88,7 @@@
      <td></td>
      <td>
        <p><%= t 'user.account.update home location on click' %> <input type="checkbox" value="1" <% unless @user.home_lat and @user.home_lon %> checked="checked" <% end %> id="updatehome" /> </p>
-       <div id="map" style="border:1px solid black; position:relative; width:500px; height:400px;"></div>
+       <div id="map" class="user_map" style="border:1px solid black; position:relative; width:500px; height:400px;"></div>
      </td>
    </tr>
    
@@@ -99,7 -96,7 +99,7 @@@
  </table>
  <% end %>
  
- <%= render :partial => 'friend_map' %>
+ <%= render :partial => 'map' %>
  
  <% unless @user.data_public? %>
  <a name="public"></a>
index 269bbc0cc6aeff99512132836d8603ddb16701b6,cf7f4a8198097a2753e4bc4d20f46cc2de4f6d85..46e7f7b8b334867c7d02ab6da592de845e05b11b
@@@ -1,17 -1,14 +1,16 @@@
 -<h1><%= t 'user.login.heading' %></h1>
 +      <h1><%= t 'user.login.heading' %></h1>
  
  <p><%= t 'user.login.please login', :create_user_link => link_to(t('user.login.create_account'), :controller => 'user', :action => 'new', :referer => params[:referer]) %></p>
  
  <% form_tag :action => 'login' do %>
  <%= hidden_field_tag('referer', h(params[:referer])) %>
  <table id="loginForm">
-   <tr><td class="fieldName"><%= t 'user.login.email or username' %></td><td><%= text_field('user', 'email',{:size => 28, :maxlength => 255, :tabindex => 1}) %></td></tr>
-   <tr><td class="fieldName"><%= t 'user.login.password' %></td><td><%= password_field('user', 'password',{:size => 28, :maxlength => 255, :tabindex => 2}) %></td><td> <span class="minorNote">(<%= link_to t('user.login.lost password link'), :controller => 'user', :action => 'lost_password' %>)</span></td></tr>
- <tr><td colspan = "3"><h4><I><%= t 'user.login.alternatively' %></I></h4></td></tr>
- <tr><td class="fieldName"><%= t 'user.login.openid' %></td><td><%= text_field('user', 'openid_url',{:size => 28, :maxlength => 255, :tabindex => 3}) %></td><td> <span class="minorNote">(<a href="<%= t 'user.account.openid.link' %>" target="_new"><%= t 'user.account.openid.link text' %></a>)</span></td></tr>
-   <tr><td colspan="2">&nbsp;<!--vertical spacer--></td></tr>
-   <tr><td colspan="2">&nbsp;<!--vertical spacer--></td></tr>
-   <tr><td class="fieldName"><label for="remember_me">Remember me:</label></td><td><%= check_box_tag "remember_me", "yes", false, :tabindex => 3 %></td><td align=right><%= submit_tag t('user.login.login_button'), :tabindex => 3 %></td></tr>
+   <tr><td class="fieldName"><%= t 'user.login.email or username' %></td><td><%= text_field('user', 'email',{:value => "", :size => 28, :maxlength => 255, :tabindex => 1}) %></td></tr>
 -  <tr><td class="fieldName"><%= t 'user.login.password' %></td><td><%= password_field('user', 'password',{:value => "", :size => 28, :maxlength => 255, :tabindex => 2}) %> <span class="minorNote">(<%= link_to t('user.login.lost password link'), :controller => 'user', :action => 'lost_password' %>)</span></td></tr>
 -  <tr><td class="fieldName"><label for="remember_me"><%= t 'user.login.remember' %></label></td><td><%= check_box_tag "remember_me", "yes", false, :tabindex => 3 %></td></tr> 
 -  <tr><td colspan="2">&nbsp;<!--vertical spacer--></td></tr>
 -  <tr><td></td><td align="right"><%= submit_tag t('user.login.login_button'), :tabindex => 3 %></td></tr>
++  <tr><td class="fieldName"><%= t 'user.login.password' %></td><td><%= password_field('user', 'password',{:value => "", :size => 28, :maxlength => 255, :tabindex => 2}) %></td><td> <span class="minorNote">(<%= link_to t('user.login.lost password link'), :controller => 'user', :action => 'lost_password' %>)</span></td></tr>
++  <tr><td colspan = "3"><h4><I><%= t 'user.login.alternatively' %></I></h4></td></tr>
++  <tr><td class="fieldName"><%= t 'user.login.openid' %></td><td><%= text_field('user', 'openid_url',{:size => 28, :maxlength => 255, :tabindex => 3}) %></td><td> <span class="minorNote">(<a href="<%= t 'user.account.openid.link' %>" target="_new"><%= t 'user.account.openid.link text' %></a>)</span></td></tr>
++  <tr><td colspan="3">&nbsp;<!--vertical spacer--></td></tr>
++  <tr><td colspan="3">&nbsp;<!--vertical spacer--></td></tr>
++  <tr><td class="fieldName"><label for="remember_me"><%= t 'user.login.remember' %></label></td><td><%= check_box_tag "remember_me", "yes", false, :tabindex => 3 %></td><td align=right><%= submit_tag t('user.login.login_button'), :tabindex => 3 %></td></tr>
  </table>
  <% end %>
index 3059a0bc0765afc5281c8c6a373969c1afb02708,bd85664c04d7c10b00e3245d648c91f74fc93af3..d2c939badc1908cf66b12e66b8295b91cdbeaedc
  <% form_tag :action => 'save' do %>
  <%= hidden_field_tag('referer', h(params[:referer])) unless params[:referer].nil? %>
  <table id="signupForm">
 -  <tr><td class="fieldName"><%= t 'user.new.email address' %></td><td><%= text_field('user', 'email',{:size => 50, :maxlength => 255, :tabindex => 1}) %></td></tr>
 +  <tr><td class="fieldName"><%= t 'user.new.email address' %></td><td><%= text_field('user', 'email',{:size => 50, :maxlength => 255, :tabindex => 1, :value => @email}) %></td></tr>
    <tr><td class="fieldName"><%= t 'user.new.confirm email address' %></td><td><%= text_field('user', 'email_confirmation',{:size => 50, :maxlength => 255, :tabindex => 2}) %></td></tr>
    <tr><td></td><td><span class="minorNote"><%= t 'user.new.not displayed publicly' %></span></td></tr>
    <tr><td colspan="2">&nbsp;<!--vertical spacer--></td></tr>
 -  <tr><td class="fieldName"><%= t 'user.new.display name' %></td><td><%= text_field('user', 'display_name',{:size => 30, :maxlength => 255, :tabindex => 3}) %></td></tr>
 +  <tr><td class="fieldName"><%= t 'user.new.display name' %></td><td><%= text_field('user', 'display_name',{:size => 30, :maxlength => 255, :tabindex => 3, :value => @nickname}) %></td></tr>
    <tr><td></td><td><span class="minorNote"><%= t 'user.new.display name description' %></span></td></tr>
    <tr><td colspan="2">&nbsp;<!--vertical spacer--></td></tr>
    <tr><td class="fieldName"><%= t 'user.new.password' %></td><td><%= password_field('user', 'pass_crypt',{:size => 30, :maxlength => 255, :tabindex => 4}) %></td></tr>
    <tr><td class="fieldName"><%= t 'user.new.confirm password' %></td><td><%= password_field('user', 'pass_crypt_confirmation',{:size => 30, :maxlength => 255, :tabindex => 5}) %></td></tr>
    
    <tr><td colspan="2">&nbsp;<!--vertical spacer--></td></tr>
 -  <tr><td></td><td align="right"><input type="submit" value="<%= t'user.new.signup' %>" tabindex="6"></td></tr>
 +      <tr><td class="fieldName"><%= t 'user.new.openID' %></td><td><%= text_field('user', 'openid_url',{:size => 50, :maxlength => 255, :tabindex => 6, :value => @openID}) %></td></tr>
 +  <tr><td></td><td><span class="minorNote"><%= t 'user.new.openID description' %></span></td></tr>
 +  <tr><td colspan="2">&nbsp;<!--vertical spacer--></td></tr>
 +  <tr><td></td><td align="right"><input type="submit" value="<%= t'user.new.signup' %>" tabindex="7"></td></tr>
  </table>
  <% end %>
  
+ <%= javascript_include_tag 'https://ethnio.com/remotes/62786' %>
  <% end %>
diff --combined config/environment.rb
index 70a1ddbd5ac04cd52ce30899a247d24b0d7e1172,539af83b2971842a09296fe6456493c4c79e3571..01115ba97fb17133e9e47eb38cb829faac36ba50
@@@ -52,8 -52,8 +52,9 @@@ Rails::Initializer.run do |config
    config.gem 'rmagick', :lib => 'RMagick'
    config.gem 'oauth', :version => '>= 0.3.6'
    config.gem 'httpclient'
 +  config.gem 'ruby-openid', :lib => 'openid', :version => '>=2.0.4'
    config.gem 'SystemTimer', :version => '>= 1.1.3', :lib => 'system_timer'
+   config.gem 'sanitize'
  
    # Only load the plugins named here, in the order given. By default, all plugins
    # in vendor/plugins are loaded in alphabetical order.
diff --combined config/locales/en.yml
index b358cf8d3b9d1b684f4673f8d266e60d17119632,8699d600cf0420e5775fe87f17c2d6356b291ed9..2df80cd1d7ccebcf56d977a9bda7f797e5f18d37
@@@ -1,6 -1,9 +1,9 @@@
  en:
    html:
      dir: ltr
+   time:
+     formats:
+       friendly: "%e %B %Y at %H:%M"    
    activerecord:
      # Translates all the model names, which is used in error handling on the web site
      models:
      with_id: "{{id}}"
      with_version: "{{id}}, v{{version}}"
      with_name: "{{name}} ({{id}})"
-   map:
-     view: View
-     edit: Edit
-     coordinates: "Coordinates:"
    browse:
      changeset:
        title: "Changeset"
        heading: "The user {{user}} does not exist"
        body: "Sorry, there is no user with the name {{user}}. Please check your spelling, or maybe the link you clicked is wrong."
      diary_entry:
-       posted_by: "Posted by {{link_user}} at {{created}} in {{language_link}}"
+       posted_by: "Posted by {{link_user}} on {{created}} in {{language_link}}"
        comment_link: Comment on this entry
        reply_link: Reply to this entry
        comment_count:
        hide_link: Hide this entry
        confirm: Confirm
      diary_comment:
-       comment_from: "Comment from {{link_user}}  at {{comment_created_at}}"
+       comment_from: "Comment from {{link_user}} on {{comment_created_at}}"
        hide_link: Hide this comment
        confirm: Confirm
+     location:
+       location: "Location:"
+       view: "View"
+       edit: "Edit"
      feed:
        user:
          title: "OpenStreetMap diary entries for {{user}}"
      friend_notification:
        subject: "[OpenStreetMap] {{user}} added you as a friend"
        had_added_you: "{{user}} has added you as a friend on OpenStreetMap."
-       see_their_profile: "You can see their profile at {{userurl}} and add them as a friend too if you wish."
+       see_their_profile: "You can see their profile at {{userurl}}."
+       befriend_them: "You can also add them as a friend at {{befriendurl}}."
      gpx_notification:
        greeting: "Hi,"
        your_gpx_file: "It looks like your GPX file"
        message_sent: "Message sent"
        limit_exceeded: "You have sent a lot of messages recently. Please wait a while before trying to send any more."
      no_such_user:
-       title: "No such user or message"
-       heading: "No such user or message"
-       body: "Sorry there is no user or message with that name or id"
+       title: "No such user"
+       heading: "No such user"
+       body: "Sorry there is no user with that name."
+     no_such_message:
+       title: "No such message"
+       heading: "No such message"
+       body: "Sorry there is no message with that id."
      outbox: 
        title: "Outbox"
        my_inbox: "My {{inbox_link}}"
        date: "Date"
        no_sent_messages: "You have no sent messages yet. Why not get in touch with some of the {{people_mapping_nearby_link}}?"
        people_mapping_nearby: "people mapping nearby"
+     reply:
+       wrong_user: "You are logged in as `{{user}}' but the message you have asked to reply to was not sent to that user. Please login as the correct user in order to reply."
      read:
        title: "Read message"
        reading_your_messages: "Reading your messages"
        reading_your_sent_messages: "Reading your sent messages"
        to: "To"
        back_to_outbox: "Back to outbox"
+       wrong_user: "You are logged in as `{{user}}' but the message you have asked to read to was not sent by or to that user. Please login as the correct user in order to read it."
      sent_message_summary:
        delete_button: "Delete"
      mark:
        heading: "Editing trace {{name}}"
        filename: "Filename:"
        download: "download"
-       uploaded_at: "Uploaded at:"
+       uploaded_at: "Uploaded:"
        points: "Points:"
        start_coord: "Start coordinate:"
        map: "map"
        pending: "PENDING"
        filename: "Filename:"
        download: "download"
-       uploaded: "Uploaded at:"
+       uploaded: "Uploaded:"
        points: "Points:"
        start_coordinates: "Start coordinate:"
        map: "map"
        create_account: "create an account"
        email or username: "Email Address or Username:"
        password: "Password:"
 +      openid: "OpenID:"
 +      openid description: "Use your OpenID to login"
 +      alternatively: "Alternatively"
+       remember: "Remember me:"
        lost password link: "Lost your password?"
        login_button: "Login"
        account not active: "Sorry, your account is not active yet.<br />Please click on the link in the account confirmation email to activate your account."
        auth failure: "Sorry, could not log in with those details."
 +      openid missing provider: "Sorry, could not contact your OpenID provider"
 +      openid invalid: "Sorry, your OpenID seems misformed"
+     logout:
+       title: "Logout"
+       heading: "Logout from OpenStreetMap"
+       logout_button: "Logout"      
      lost_password:
        title: "Lost password"
        heading: "Forgotten Password?"
        display name description: "Your publicly displayed username. You can change this later in the preferences."
        password: "Password:"
        confirm password: "Confirm Password:"
 +      openID: "OpenID:"
 +      openID description: '(Optional) If you have an <a href="http://wiki.openstreetmap.org/wiki/openID">OpenID</a> you can associate it with this account to login'
        signup: Signup
        flash create success message: "User was successfully created. Check your email for a confirmation note, and you will be mapping in no time :-)<br /><br />Please note that you will not be able to login until you've received and confirmed your email address.<br /><br />If you use an antispam system which sends confirmation requests then please make sure you whitelist webmaster@openstreetmap.org as we are unable to reply to any confirmation requests."
      no_such_user:
        my edits: my edits
        my traces: my traces
        my settings: my settings
+       oauth settings: oauth settings
        blocks on me: blocks on me
        blocks by me: blocks by me
        send message: send message
        created from: "Created from:"
        description: Description
        user location: User location
-       no home location: "No home location has been set."
-       if set location: "If you set your location, a pretty map and stuff will appear below. You can set your home location on your {{settings_link}} page."
+       if set location: "If you set your location, a pretty map and stuff will appear here. You can set your home location on your {{settings_link}} page."
        settings_link_text: settings
        your friends: Your friends
        no friends: You have not added any friends yet.
        km away: "{{count}}km away"
        m away: "{{count}}m away"
-       nearby users: "Nearby users:"
-       no nearby users: "There are no users who admit to mapping nearby yet."
-       my_oauth_details: "View my OAuth details"
+       nearby users: "Other nearby users"
+       no nearby users: "There are no other users who admit to mapping nearby yet."
        role:
          administrator: "This user is an administrator"
          moderator: "This user is a moderator"
        unhide_user: "unhide this user"
        delete_user: "delete this user"
        confirm: "Confirm"
-     friend_map:
-       your location: Your location
-       nearby mapper: "Nearby mapper: [[nearby_user]]"
+     popup:
+       your location: "Your location"
+       nearby mapper: "Nearby mapper"
+       friend: "Friend"
      account:
        title: "Edit account"
        my settings: My settings
        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."
        keep image: "Keep the current image"
        delete image: "Remove the current image"
        replace image: "Replace the current image"
+       image size hint: "(square images at least 100x100 work best)"
        home location: "Home Location:"
        no home location: "You have not entered your home location."
        latitude: "Latitude:"