Ticket #2500: openid.patch

File openid.patch, 15.0 KB (added by amm, 10 years ago)

Initial version of a patch that implements login via OpenID

  • app/controllers/user_controller.rb

    diff -N -r -U 2 /usr/local/src/svn/openstreetmap/sites/rails_port/app/controllers/user_controller.rb ./app/controllers/user_controller.rb
    old new  
    4545    @tokens = @user.oauth_tokens.find :all, :conditions => 'oauth_tokens.invalidated_at is null and oauth_tokens.authorized_at is not null'
    4646
     47    #The redirect from the OpenID provider reenters here again
     48    #and we need to pass the parameters through to the
     49    #open_id_authentication function
     50    if params[:open_id_complete]
     51      openid_verify('')
     52      return
     53    end
     54
    4755    if params[:user] and params[:user][:display_name] and params[:user][:description]
    4856      if params[:user][:email] != @user.email
     
    5664        @user.pass_crypt_confirmation = params[:user][:pass_crypt_confirmation]
    5765      end
     66      if (params[:user][:identity_url].length == 0)
     67        #Clearing doesn't need OpenID validation, so we can set it here.
     68        @user.identity_url = ''
     69      end
    5870
    5971      @user.description = params[:user][:description]
     
    7284        end
    7385      end
     86
     87      if (params[:user][:identity_url].length > 0)
     88        @norm_identity_url = OpenIdAuthentication.normalize_identifier(params[:user][:identity_url])
     89        if (@norm_identity_url != @user.identity_url)
     90          #If the OpenID has changed, we want to check that it is a valid OpenID and one
     91          #the user has control over before saving the openID as a password equivalent for
     92          #the user.
     93          openid_verify(@norm_identity_url)
     94        end
     95      end
    7496    end
    7597  end
    7698
     99  def openid_specialcase_mapping(openid_url)
     100    #Special case gmail.com, as it is pontentially a popular OpenID provider and unlike
     101    #yahoo.com, where it works automatically, Google have hidden their OpenID endpoint
     102    #somewhere obscure making it less userfriendly.
     103    if (openid_url.match(/(.*)gmail.com$/) or openid_url.match(/(.*)googlemail.com$/) )
     104      return 'https://google.com/accounts/o8/id'
     105    end
     106
     107    return nil
     108  end 
     109
     110  def openid_verify(openid_url)
     111    authenticate_with_open_id(openid_url) do |result, identity_url|
     112      if result.successful?
     113        #We need to use the openid url passed back from the OpenID provider
     114        #rather than the one supplied by the user, as these can be different.
     115        #e.g. one can simply enter yahoo.com in the login box, i.e. no user specific url
     116        #only once it comes back from the OpenID provider do we know the unique address for
     117        #the user.
     118        @user.identity_url = identity_url
     119        if @user.save
     120          flash.now[:notice] = t 'user.account.flash update success'
     121        end
     122      else if result.missing?
     123             mapped_id = openid_specialcase_mapping(openid_url)
     124             if mapped_id
     125               open_id_verify(mapped_id)
     126             else
     127               flash.now[:error] = t 'user.login.openid missing provider'
     128             end
     129           else if result.invalid?
     130                  flash.now[:error] = t 'user.login.openid invalid'
     131                else
     132                  flash.now[:error] = t 'user.login.auth failure'
     133                end
     134           end
     135      end
     136    end
     137  end
     138
     139
    77140  def set_home
    78141    if params[:user][:home_lat] and params[:user][:home_lon]
     
    143206    # send them to the home page
    144207    redirect_to :controller => 'site', :action => 'index' if session[:user]
     208
     209    @nickname = params['nickname']
     210    @email = params['email']
    145211  end
    146212
    147213  def login
     214
     215    #The redirect from the OpenID provider reenters here again
     216    #and we need to pass the parameters through to the
     217    # open_id_authentication function
     218    if params[:open_id_complete]
     219      open_id_authentication('')
     220    end
     221
     222   
    148223    if params[:user] and session[:user].nil?
    149       email_or_display_name = params[:user][:email]
    150       pass = params[:user][:password]
    151       user = User.authenticate(:username => email_or_display_name, :password => pass)
    152       if user
    153         session[:user] = user.id
    154       elsif User.authenticate(:username => email_or_display_name, :password => pass, :inactive => true)
    155         flash.now[:error] = t 'user.login.account not active'
     224
     225      if !params[:user][:identity_url].empty?
     226        open_id_authentication(params[:user][:identity_url])
    156227      else
    157         flash.now[:error] = t 'user.login.auth failure'
     228        email_or_display_name = params[:user][:email]
     229        pass = params[:user][:password]
     230        user = User.authenticate(:username => email_or_display_name, :password => pass)
     231        if user
     232          session[:user] = user.id
     233        elsif User.authenticate(:username => email_or_display_name, :password => pass, :inactive => true)
     234          flash.now[:error] = t 'user.login.account not active'
     235        else
     236          flash.now[:error] = t 'user.login.auth failure'
     237        end
    158238      end
    159239    end
    160 
     240 
    161241    if session[:user]
    162242      # The user is logged in, if the referer param exists, redirect them to that
     
    178258  end
    179259
     260  def open_id_authentication(openid_url)
     261    user1 = User.find_by_identity_url(openid_url)
     262    #TODO: only ask for nickname and email, if we don't already have a user for that openID, in which case
     263    #email and nickname are already filled out. I don't know how to do that with ruby syntax though, as we
     264    #don't want to duplicate the do block
     265    #On the otherhand it also doesn't matter too much if we ask everytime, as the OpenID provider should
     266    #remember these results, and shouldn't repromt the user for these data each time.
     267    authenticate_with_open_id(openid_url, :return_to => 'http://' + request.host + '/login?referer=' + params[:referer], :optional => [:nickname, :email]) do |result, identity_url, registration|
     268      if result.successful?
     269        #We need to use the openid url passed back from the OpenID provider
     270        #rather than the one supplied by the user, as these can be different.
     271        #e.g. one can simply enter yahoo.com in the login box, i.e. no user specific url
     272        #only once it comes back from the OpenID provider do we know the unique address for
     273        #the user.
     274        user = User.find_by_identity_url(identity_url)
     275        if user
     276          if user.visible? and user.active?
     277            session[:user] = user.id
     278          else
     279            user = nil
     280            flash.now[:error] = t 'user.login.account not active'
     281          end
     282        else
     283          #We don't have a user registered to this OpenID. Redirect to the create account page
     284          #with username and email filled in if they have been given by the OpenID provider through
     285          #the simple registration protocol
     286          redirect_to :controller => 'user', :action => 'new', :nickname => registration['nickname'], :email => registration['email']
     287        end
     288      else if result.missing?
     289             #Try and apply some heuristics to make common cases more userfriendly
     290             mapped_id = openid_specialcase_mapping(openid_url)
     291             if mapped_id
     292               open_id_authentication(mapped_id)
     293             else
     294               flash.now[:error] = t 'user.login.openid missing provider'
     295             end
     296           else if result.invalid?
     297                  flash.now[:error] = t 'user.login.openid invalid'
     298                else
     299                  flash.now[:error] = t 'user.login.auth failure'
     300                end
     301           end
     302      end
     303    end
     304  end
     305
    180306  def logout
    181307    if session[:token]
  • app/views/user/account.html.erb

    diff -N -r -U 2 /usr/local/src/svn/openstreetmap/sites/rails_port/app/views/user/account.html.erb ./app/views/user/account.html.erb
    old new  
    66  <tr><td class="fieldName"><%= t 'user.new.email address' %></td><td><%= f.text_field :email, {:size => 50, :maxlength => 255} %> <span class="minorNote"><%= t 'user.account.email never displayed publicly' %></span></td></tr>
    77  <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></tr>
    8   <tr><td class="fieldName"><%= t 'user.new.confirm password' %></td><td><%= f.password_field :pass_crypt_confirmation, {:value => '', :size => 30, :maxlength => 255} %></td></tr>
     8  <tr><td class="fieldName" style="padding-bottom:0px;"><%= t 'user.new.confirm password' %></td><td><%= f.password_field :pass_crypt_confirmation, {:value => '', :size => 30, :maxlength => 255} %></td></tr>
     9  <tr><td class="fieldName" ><%= t 'user.account.openid.openid' %></td><td style="padding-bottom:0px;"><%= f.text_field :identity_url %> (<a href="<%= t 'user.account.openid.link' %>" target="_new"><%= t 'user.account.openid.link text' %></a>) </td></tr>
    910
    1011  <tr>
  • app/views/user/login.html.erb

    diff -N -r -U 2 /usr/local/src/svn/openstreetmap/sites/rails_port/app/views/user/login.html.erb ./app/views/user/login.html.erb
    old new  
    1 <h1><%= t 'user.login.heading' %></h1>
     1        <h1><%= t 'user.login.heading' %></h1>
    22
    33<p><%= t 'user.login.please login', :create_user_link => link_to(t('user.login.create_account'), :controller => 'user', :action => 'new', :referer => params[:referer]) %></p>
     
    88  <tr><td class="fieldName"><%= t 'user.login.email or username' %></td><td><%= text_field('user', 'email',{:size => 28, :maxlength => 255, :tabindex => 1}) %></td></tr>
    99  <tr><td class="fieldName"><%= t 'user.login.password' %></td><td><%= password_field('user', 'password',{: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>
    10   <tr><td colspan="2">&nbsp;<!--vertical spacer--></td></tr>
    11   <tr><td></td><td align="right"><%= submit_tag t('user.login.login_button'), :tabindex => 3 %></td></tr>
     10<tr><td colspan="2">&nbsp;<!--vertical spacer--></td></tr>
     11<tr><td></td><td align="right"><%= submit_tag t('user.login.login_button'), :tabindex => 3 %></td></tr>
     12<tr><td colspan = "3"><h4><%= t 'user.login.alternatively' %></h4></td></tr>
     13
     14
     15<tr><td colspan="2"><%= t 'user.login.openid description' %> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;(<a href="<%= t 'user.account.openid.link' %>" target="_new"><%= t 'user.account.openid.link text' %></a>)</td></tr>
     16
     17<tr><td class="fieldName"><%= t 'user.login.openid' %></td><td><%= text_field('user', 'identity_url',{:size => 28, :maxlength => 255, :tabindex => 3}) %></td></tr>
     18<tr><td colspan="2">&nbsp;<!--vertical spacer--></td></tr>
     19<tr><td></td><td align="right"><%= submit_tag t('user.login.login_button'), :tabindex => 3 %></td></tr>
    1220</table>
    1321<% end %>
  • app/views/user/new.html.erb

    diff -N -r -U 2 /usr/local/src/svn/openstreetmap/sites/rails_port/app/views/user/new.html.erb ./app/views/user/new.html.erb
    old new  
    2222<%= hidden_field_tag('referer', h(params[:referer])) unless params[:referer].nil? %>
    2323<table id="signupForm">
    24   <tr><td class="fieldName"><%= t 'user.new.email address' %></td><td><%= text_field('user', 'email',{:size => 50, :maxlength => 255, :tabindex => 1}) %></td></tr>
     24  <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>
    2525  <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>
    2626  <tr><td></td><td><span class="minorNote"><%= t 'user.new.not displayed publicly' %></span></td></tr>
    2727  <tr><td colspan="2">&nbsp;<!--vertical spacer--></td></tr>
    28   <tr><td class="fieldName"><%= t 'user.new.display name' %></td><td><%= text_field('user', 'display_name',{:size => 30, :maxlength => 255, :tabindex => 3}) %></td></tr>
     28  <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>
    2929  <tr><td></td><td><span class="minorNote"><%= t 'user.new.display name description' %></span></td></tr>
    3030  <tr><td colspan="2">&nbsp;<!--vertical spacer--></td></tr>
  • config/locales/en.yml

    diff -N -r -U 2 /usr/local/src/svn/openstreetmap/sites/rails_port/config/locales/en.yml ./config/locales/en.yml
    old new  
    13321332      email or username: "Email Address or Username:"
    13331333      password: "Password:"
     1334      openid: "OpenID:"
     1335      openid description: "Use your OpenID to login"
     1336      alternatively: "Alternatively"
    13341337      lost password link: "Lost your password?"
    13351338      login_button: "Login"
    13361339      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."
    13371340      auth failure: "Sorry, could not log in with those details."
     1341      openid missing provider: "Sorry, could not contact your OpenID provider"
     1342      openid invalid: "Sorry, your OpenID seems misformed"
    13381343    lost_password:
    13391344      title: "Lost password"
     
    14321437      my settings: My settings
    14331438      email never displayed publicly: "(never displayed publicly)"
     1439      openid:
     1440        openid: "OpenID:"
     1441        link: "http://wiki.openstreetmap.org/wiki/OpenID"
     1442        link text: "what is this?"
    14341443      public editing:
    14351444        heading: "Public editing:"
  • db/migrate/048_add_open_id_authentication_tables.rb

    diff -N -r -U 2 /usr/local/src/svn/openstreetmap/sites/rails_port/db/migrate/048_add_open_id_authentication_tables.rb ./db/migrate/048_add_open_id_authentication_tables.rb
    old new  
     1class AddOpenIdAuthenticationTables < ActiveRecord::Migration
     2  def self.up
     3    create_table :open_id_authentication_associations, :force => true do |t|
     4      t.integer :issued, :lifetime
     5      t.string :handle, :assoc_type
     6      t.binary :server_url, :secret
     7    end
     8
     9    create_table :open_id_authentication_nonces, :force => true do |t|
     10      t.integer :timestamp, :null => false
     11      t.string :server_url, :null => true
     12      t.string :salt, :null => false
     13    end
     14   
     15    add_column :users, :identity_url, :string
     16  end
     17
     18  def self.down
     19    remove_column :users, :identity_url
     20    drop_table :open_id_authentication_associations
     21    drop_table :open_id_authentication_nonces
     22  end
     23end