From: Tom Hughes Date: Mon, 4 Oct 2010 23:23:04 +0000 (+0100) Subject: Merge branch 'master' into openid X-Git-Tag: live~6806 X-Git-Url: https://git.openstreetmap.org./rails.git/commitdiff_plain/c016bf6c153eb1c416b2104874bf2e44c2f55913?hp=-c Merge branch 'master' into openid Conflicts: app/controllers/user_controller.rb --- c016bf6c153eb1c416b2104874bf2e44c2f55913 diff --combined app/controllers/user_controller.rb index a42f932a9,e5a2f04bd..1ac3b1ca5 --- a/app/controllers/user_controller.rb +++ b/app/controllers/user_controller.rb @@@ -26,47 -26,17 +26,47 @@@ class UserController < ApplicationContr render :update do |page| page.replace_html "contributorTerms", :partial => "terms", :locals => { :has_decline => params[:has_decline] } 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? + # Something is wrong, so rerender the form render :action => :new 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 @@@ -100,8 -70,8 +100,8 @@@ @user.terms_agreed = Time.now.getutc if @user.save - flash[:notice] = t 'user.new.flash create success message' + 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))) redirect_to :action => 'login' else render :action => 'new' @@@ -133,25 -103,29 +133,25 @@@ @user.home_lat = params[:user][:home_lat] @user.home_lon = params[:user][:home_lon] - if @user.save - set_locale + @user.openid_url = nil if params[:user][:openid_url].empty? - 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' - - 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 - else - if flash[:errors] - flash[:errors].each do |attr,msg| - attr = "new_email" if attr == "email" - @user.errors.add(attr,msg) - 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 end end @@@ -210,26 -184,41 +210,26 @@@ def new @title = t 'user.new.title' - - # The user is logged in already, so don't show them the signup - # page, instead send them to the home page - redirect_to :controller => 'site', :action => 'index' if session[:user] + @referer = params[:referer] || session[:referer] + + if session[:user] + # The user is logged in already, so don't show them the signup + # page, instead send them to the home page + redirect_to :controller => 'site', :action => 'index' + elsif not params['openid'].nil? + flash.now[:notice] = t 'user.new.openid association' + end end def login - @title = t 'user.login.title' - - if params[:user] - email_or_display_name = params[:user][:email] - pass = params[:user][:password] - user = User.authenticate(:username => email_or_display_name, :password => pass) + if params[:username] or using_open_id? + session[:remember_me] ||= params[:remember_me] + session[:referer] ||= params[:referer] - if user - session[:user] = user.id - session_expires_after 1.month if params[:remember_me] - - # The user is logged in, if the referer param exists, redirect - # them to that unless they've also got a block on them, in - # which case redirect them to the block so they can clear it. - if user.blocked_on_view - redirect_to user.blocked_on_view, :referer => params[:referer] - elsif params[:referer] - redirect_to params[:referer] - else - redirect_to :controller => 'site', :action => 'index' - 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' @@@ -258,30 -247,57 +258,57 @@@ end def confirm - if params[:confirm_action] - token = UserToken.find_by_token(params[:confirm_string]) - if token and !token.user.active? - @user = token.user - @user.status = "active" - @user.email_valid = true - @user.save! - referer = token.referer - token.destroy - flash[:notice] = t 'user.confirm.success' - session[:user] = @user.id - unless referer.nil? - redirect_to referer + if request.post? + if token = UserToken.find_by_token(params[:confirm_string]) + if token.user.active? + flash[:error] = t('user.confirm.already active') + redirect_to :action => 'login' else - redirect_to :action => 'account', :display_name => @user.display_name + user = token.user + user.status = "active" + user.email_valid = true + user.save! + referer = token.referer + token.destroy + session[:user] = user.id + + unless referer.nil? + flash[:notice] = t('user.confirm.success') + redirect_to referer + else + flash[:notice] = t('user.confirm.success') + "

" + t('user.confirm.before you start') + redirect_to :action => 'account', :display_name => user.display_name + end end else - flash.now[:error] = t 'user.confirm.failure' + user = User.find_by_display_name(params[:display_name]) + + if user and user.active? + flash[:error] = t('user.confirm.already active') + elsif user + flash[:error] = t('user.confirm.unknown token') + t('user.confirm.reconfirm', :reconfirm => url_for(:action => 'confirm_resend', :display_name => params[:display_name])) + else + flash[:error] = t('user.confirm.unknown token') + end + + redirect_to :action => 'login' end end end + def confirm_resend + if user = User.find_by_display_name(params[:display_name]) + Notifier.deliver_signup_confirm(user, user.tokens.create) + flash[:notice] = t 'user.confirm_resend.success', :email => user.email + else + flash[:notice] = t 'user.confirm_resend.failure', :name => params[:display_name] + end + + redirect_to :action => 'login' + end + def confirm_email - if params[:confirm_action] + if request.post? token = UserToken.find_by_token(params[:confirm_string]) if token and token.user.new_email? @user = token.user @@@ -297,7 -313,8 +324,8 @@@ session[:user] = @user.id redirect_to :action => 'account', :display_name => @user.display_name else - flash.now[:error] = t 'user.confirm_email.failure' + flash[:error] = t 'user.confirm_email.failure' + redirect_to :action => 'account', :display_name => @user.display_name end end end @@@ -406,164 -423,6 +434,164 @@@ private + ## + # handle password authentication + def password_authentication(username, password) + if user = User.authenticate(:username => username, :password => password) + successful_login(user) - elsif User.authenticate(:username => username, :password => password, :pending => true) - failed_login t('user.login.account not active') ++ elsif user = 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] + + if user.blocked_on_view + redirect_to user.blocked_on_view, :referer => params[:referer] + elsif session[:referer] + redirect_to session[:referer] + else + redirect_to :controller => 'site', :action => 'index' + end + + session.delete(:remember_me) + session.delete(:referer) + end + + ## + # process a failed login + def failed_login(message) + flash[:error] = message + + redirect_to :action => 'login', :referer => session[:referer] + + session.delete(:remember_me) + session.delete(:referer) + end + + ## + # update a user's details + def update_user(user) + if user.save + set_locale + + if user.new_email.nil? or user.new_email.empty? + flash.now[:notice] = t 'user.account.flash update success' + else + flash.now[:notice] = t 'user.account.flash update success confirm needed' + + begin + Notifier.deliver_email_confirm(user, user.tokens.create) + rescue + # Ignore errors sending email + end + end + end + end + ## # require that the user is a administrator, or fill out a helpful error message # and return them to the user page. diff --combined config/locales/en.yml index 8a3a1a2ec,4e633b27b..7625a695f --- a/config/locales/en.yml +++ b/config/locales/en.yml @@@ -1497,39 -1497,14 +1497,39 @@@ en create_account: "create an account" email or username: "Email Address or Username:" password: "Password:" + openid: "{{logo}} OpenID:" + username_heading: "Login with username and password:" + openid_heading: "Login with OpenID:" remember: "Remember me:" lost password link: "Lost your password?" login_button: "Login" - account not active: "Sorry, your account is not active yet.
Please click on the link in the account confirmation email to activate your account." + account not active: "Sorry, your account is not active yet.
Please use the link in the account confirmation email to activate your account, or request a new confirmation email." account suspended: Sorry, your account has been suspended due to suspicious activity.
Please contact the {{webmaster}} if you wish to discuss this. webmaster: webmaster auth failure: "Sorry, could not log in with those details." notice: "Find out more about OpenStreetMap's upcoming license change (translations) (discussion)" + openid missing provider: "Sorry, could not contact your OpenID provider" + openid invalid: "Sorry, your OpenID seems to be malformed" + openid_logo_alt: "Log in with an OpenID" + openid_providers: + openid: + title: Login with an OpenID URL + alt: Login with an OpenID URL + yahoo: + title: Login with a Yahoo! OpenID + alt: Login with a Yahoo! OpenID + google: + title: Login with a Google OpenID + alt: Login with a Google OpenID + myopenid: + title: Login with a myOpenID OpenID + alt: Login with a myOpenID OpenID + wordpress: + title: Login with a Wordpress.com OpenID + alt: Login with a Wordpress.com OpenID + myspace: + title: Login with a MySpace OpenID + alt: Login with a MySpace OpenID logout: title: "Logout" heading: "Logout from OpenStreetMap" @@@ -1562,23 -1537,10 +1562,23 @@@ not displayed publicly: 'Not displayed publicly (see privacy policy)' 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: | +

Your OpenID is not associated with a OpenStreetMap account yet.

+ continue: Continue - flash create success message: "User was successfully created. Check your email for a confirmation note, and you will be mapping in no time :-)

Please note that you will not be able to login until you've received and confirmed your email address.

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." + 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.

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!" terms: title: "Contributor terms" @@@ -1659,10 -1621,6 +1659,10 @@@ 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." @@@ -1704,7 -1662,13 +1704,13 @@@ press confirm button: "Press the confirm button below to activate your account." button: Confirm success: "Confirmed your account, thanks for signing up!" - failure: "A user account with this token has already been confirmed." + before you start: "We know you're probably in a hurry to start mapping, but before you do you might like to fill in some more information about yourself in the form below." + already active: "This account has already been confirmed." + unknown token: "That token doesn't seem to exist." + reconfirm: "If it's been a while since you signed up you might need to send yourself a new confirmation email." + confirm_resend: + success: "We've sent a new confirmation note to {{email}} and as soon as you confirm your account you'll be able to get mapping.

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." + failure: "User {{name}} not found." confirm_email: heading: Confirm a change of email address press confirm button: "Press the confirm button below to confirm your new email address." diff --combined test/integration/user_creation_test.rb index 86fddf7b2,01a7ca649..801321dd1 --- a/test/integration/user_creation_test.rb +++ b/test/integration/user_creation_test.rb @@@ -94,107 -94,8 +94,107 @@@ class UserCreationTest < ActionControll 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 + + # 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/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//traces + follow_redirect! + 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 @@@ -203,7 -104,7 +203,7 @@@ assert_equal register_email.to[0], new_email # Check that the confirm account url is correct - confirm_regex = Regexp.new("/user/confirm\\?confirm_string=([a-zA-Z0-9]*)") + confirm_regex = Regexp.new("/user/redirect_tester/confirm\\?confirm_string=([a-zA-Z0-9]*)") assert_match(confirm_regex, register_email.body) confirm_string = confirm_regex.match(register_email.body)[1]