Merge branch 'master' into openid
[rails.git] / app / controllers / user_controller.rb
1 class UserController < ApplicationController
2   layout 'site', :except => :api_details
3
4   before_filter :authorize, :only => [:api_details, :api_gpx_files]
5   before_filter :authorize_web, :except => [:api_details, :api_gpx_files]
6   before_filter :set_locale, :except => [:api_details, :api_gpx_files]
7   before_filter :require_user, :only => [:account, :go_public, :make_friend, :remove_friend]
8   before_filter :check_database_readable, :except => [:api_details, :api_gpx_files]
9   before_filter :check_database_writable, :only => [:login, :new, :account, :go_public, :make_friend, :remove_friend]
10   before_filter :check_api_readable, :only => [:api_details, :api_gpx_files]
11   before_filter :require_allow_read_prefs, :only => [:api_details]
12   before_filter :require_allow_read_gpx, :only => [:api_gpx_files]
13   before_filter :require_cookies, :only => [:login, :confirm]
14   before_filter :require_administrator, :only => [:set_status, :delete, :list]
15   before_filter :lookup_this_user, :only => [:set_status, :delete]
16
17   filter_parameter_logging :password, :pass_crypt, :pass_crypt_confirmation
18
19   cache_sweeper :user_sweeper, :only => [:account, :set_status, :delete], :unless => STATUS == :database_offline
20
21   def terms
22     @legale = params[:legale] || OSM.IPToCountry(request.remote_ip) || DEFAULT_LEGALE
23     @text = OSM.legal_text_for_country(@legale)
24
25     if request.xhr?
26       render :update do |page|
27         page.replace_html "contributorTerms", :partial => "terms", :locals => { :has_decline => params[:has_decline] }
28       end
29     elsif using_open_id?
30       # The redirect from the OpenID provider reenters here
31       # again and we need to pass the parameters through to
32       # the open_id_authentication function
33       @user = session.delete(:new_user)
34
35       openid_verify(nil, @user) do |user|
36       end
37
38       if @user.openid_url.nil? or @user.invalid?
39         render :action => 'new'
40       else
41         render :action => 'terms'
42       end
43     else
44       session[:referer] = params[:referer]
45
46       @title = t 'user.terms.title'
47       @user = User.new(params[:user]) if params[:user]
48
49       if params[:user] and params[:user][:openid_url] and @user.pass_crypt.empty?
50         # We are creating an account with OpenID and no password
51         # was specified so create a random one
52         @user.pass_crypt = ActiveSupport::SecureRandom.base64(16) 
53         @user.pass_crypt_confirmation = @user.pass_crypt 
54       end
55
56       if @user
57         if @user.invalid?
58           if @user.new_record?
59             # Something is wrong with a new user, so rerender the form
60             render :action => :new
61           else
62             # Error in existing user, so go to account settings
63             flash[:errors] = @user.errors
64             redirect_to :action => :account, :display_name => @user.display_name
65           end
66         elsif @user.terms_agreed?
67           # Already agreed to terms, so just show settings
68           redirect_to :action => :account, :display_name => @user.display_name
69         elsif params[:user] and params[:user][:openid_url]
70           # Verify OpenID before moving on
71           session[:new_user] = @user
72           openid_verify(params[:user][:openid_url], @user)
73         end
74       else
75         # Not logged in, so redirect to the login page
76         redirect_to :action => :login, :referer => request.request_uri
77       end
78     end
79   end
80
81   def save
82     @title = t 'user.new.title'
83
84     if Acl.find_by_address(request.remote_ip, :conditions => {:k => "no_account_creation"})
85       render :action => 'new'
86     elsif params[:decline]
87       redirect_to t('user.terms.declined')
88     elsif @user
89       if !@user.terms_agreed?
90         @user.consider_pd = params[:user][:consider_pd]
91         @user.terms_agreed = Time.now.getutc
92         if @user.save
93           flash[:notice] = t 'user.new.terms accepted'
94         end
95       end
96
97       redirect_to :action => :account, :display_name => @user.display_name
98     else
99       @user = User.new(params[:user])
100
101       @user.status = "pending"
102       @user.data_public = true
103       @user.description = "" if @user.description.nil?
104       @user.creation_ip = request.remote_ip
105       @user.languages = request.user_preferred_languages
106       @user.terms_agreed = Time.now.getutc
107
108       if @user.save
109         flash[:notice] = t 'user.new.flash create success message', :email => @user.email
110         Notifier.deliver_signup_confirm(@user, @user.tokens.create(:referer => session.delete(:referer)))
111         redirect_to :action => 'login'
112       else
113         render :action => 'new'
114       end
115     end
116   end
117
118   def account
119     @title = t 'user.account.title'
120     @tokens = @user.oauth_tokens.find :all, :conditions => 'oauth_tokens.invalidated_at is null and oauth_tokens.authorized_at is not null'
121
122     if params[:user] and params[:user][:display_name] and params[:user][:description]
123       @user.display_name = params[:user][:display_name]
124       @user.new_email = params[:user][:new_email]
125
126       if params[:user][:pass_crypt].length > 0 or params[:user][:pass_crypt_confirmation].length > 0
127         @user.pass_crypt = params[:user][:pass_crypt]
128         @user.pass_crypt_confirmation = params[:user][:pass_crypt_confirmation]
129       end
130
131       @user.description = params[:user][:description]
132       @user.languages = params[:user][:languages].split(",")
133
134       case params[:image_action]
135         when "new" then @user.image = params[:user][:image]
136         when "delete" then @user.image = nil
137       end
138
139       @user.home_lat = params[:user][:home_lat]
140       @user.home_lon = params[:user][:home_lon]
141
142       @user.openid_url = nil if params[:user][:openid_url].empty?
143
144       if params[:user][:openid_url].length > 0 and
145          params[:user][:openid_url] != @user.openid_url
146         # If the OpenID has changed, we want to check that it is a
147         # valid OpenID and one the user has control over before saving
148         # it as a password equivalent for the user.
149         session[:new_user] = @user
150         openid_verify(params[:user][:openid_url], @user)
151       else
152         update_user(@user)
153       end
154     elsif using_open_id?
155       # The redirect from the OpenID provider reenters here
156       # again and we need to pass the parameters through to
157       # the open_id_authentication function
158       @user = session.delete(:new_user)
159       openid_verify(nil, @user) do |user|
160         update_user(user)
161       end
162     else
163       if flash[:errors]
164         flash[:errors].each do |attr,msg|
165           attr = "new_email" if attr == "email" and !@user.new_email.nil?
166           @user.errors.add(attr,msg)
167         end
168       end
169     end
170   end
171
172   def go_public
173     @user.data_public = true
174     @user.save
175     flash[:notice] = t 'user.go_public.flash success'
176     redirect_to :controller => 'user', :action => 'account', :display_name => @user.display_name
177   end
178
179   def lost_password
180     @title = t 'user.lost_password.title'
181
182     if params[:user] and params[:user][:email]
183       user = User.find_by_email(params[:user][:email], :conditions => {:status => ["pending", "active", "confirmed"]})
184
185       if user
186         token = user.tokens.create
187         Notifier.deliver_lost_password(user, token)
188         flash[:notice] = t 'user.lost_password.notice email on way'
189         redirect_to :action => 'login'
190       else
191         flash.now[:error] = t 'user.lost_password.notice email cannot find'
192       end
193     end
194   end
195
196   def reset_password
197     @title = t 'user.reset_password.title'
198
199     if params[:token]
200       token = UserToken.find_by_token(params[:token])
201
202       if token
203         @user = token.user
204
205         if params[:user]
206           @user.pass_crypt = params[:user][:pass_crypt]
207           @user.pass_crypt_confirmation = params[:user][:pass_crypt_confirmation]
208           @user.status = "active" if @user.status == "pending"
209           @user.email_valid = true
210
211           if @user.save
212             token.destroy
213             flash[:notice] = t 'user.reset_password.flash changed'
214             redirect_to :action => 'login'
215           end
216         end
217       else
218         flash[:error] = t 'user.reset_password.flash token bad'
219         redirect_to :action => 'lost_password'
220       end
221     end
222   end
223
224   def new
225     @title = t 'user.new.title'
226     @referer = params[:referer] || session[:referer]
227
228     if session[:user]
229       # The user is logged in already, so don't show them the signup
230       # page, instead send them to the home page
231       redirect_to :controller => 'site', :action => 'index'
232     elsif not params['openid'].nil?
233       flash.now[:notice] = t 'user.new.openid association'
234     end
235   end
236
237   def login
238     if params[:username] or using_open_id?
239       session[:remember_me] ||= params[:remember_me]
240       session[:referer] ||= params[:referer]
241
242       if using_open_id?
243         openid_authentication(params[:openid_url])
244       else
245         password_authentication(params[:username], params[:password])
246       end
247     elsif flash[:notice].nil?
248       flash.now[:notice] =  t 'user.login.notice'
249     end
250   end
251
252   def logout
253     @title = t 'user.logout.title'
254
255     if params[:session] == request.session_options[:id]
256       if session[:token]
257         token = UserToken.find_by_token(session[:token])
258         if token
259           token.destroy
260         end
261         session[:token] = nil
262       end
263       session[:user] = nil
264       session_expires_automatically
265       if params[:referer]
266         redirect_to params[:referer]
267       else
268         redirect_to :controller => 'site', :action => 'index'
269       end
270     end
271   end
272
273   def confirm
274     if request.post?
275       if token = UserToken.find_by_token(params[:confirm_string])
276         if token.user.active?
277           flash[:error] = t('user.confirm.already active')
278           redirect_to :action => 'login'
279         else
280           user = token.user
281           user.status = "active"
282           user.email_valid = true
283           user.save!
284           referer = token.referer
285           token.destroy
286           session[:user] = user.id
287
288           unless referer.nil?
289             flash[:notice] = t('user.confirm.success')
290             redirect_to referer
291           else
292             flash[:notice] = t('user.confirm.success') + "<br /><br />" + t('user.confirm.before you start')
293             redirect_to :action => 'account', :display_name => user.display_name
294           end
295         end
296       else
297         user = User.find_by_display_name(params[:display_name])
298
299         if user and user.active?
300           flash[:error] = t('user.confirm.already active')
301         elsif user
302           flash[:error] = t('user.confirm.unknown token') + t('user.confirm.reconfirm', :reconfirm => url_for(:action => 'confirm_resend', :display_name => params[:display_name]))
303         else
304           flash[:error] = t('user.confirm.unknown token')
305         end
306
307         redirect_to :action => 'login'
308       end
309     end
310   end
311
312   def confirm_resend
313     if user = User.find_by_display_name(params[:display_name])
314       Notifier.deliver_signup_confirm(user, user.tokens.create)
315       flash[:notice] = t 'user.confirm_resend.success', :email => user.email
316     else
317       flash[:notice] = t 'user.confirm_resend.failure', :name => params[:display_name]
318     end
319
320     redirect_to :action => 'login'
321   end
322
323   def confirm_email
324     if request.post?
325       token = UserToken.find_by_token(params[:confirm_string])
326       if token and token.user.new_email?
327         @user = token.user
328         @user.email = @user.new_email
329         @user.new_email = nil
330         @user.email_valid = true
331         if @user.save
332           flash[:notice] = t 'user.confirm_email.success'
333         else
334           flash[:errors] = @user.errors
335         end
336         token.destroy
337         session[:user] = @user.id
338         redirect_to :action => 'account', :display_name => @user.display_name
339       else
340         flash[:error] = t 'user.confirm_email.failure'
341         redirect_to :action => 'account', :display_name => @user.display_name
342       end
343     end
344   end
345
346   def api_gpx_files
347     doc = OSM::API.new.get_xml_doc
348     @user.traces.each do |trace|
349       doc.root << trace.to_xml_node() if trace.public? or trace.user == @user
350     end
351     render :text => doc.to_s, :content_type => "text/xml"
352   end
353
354   def view
355     @this_user = User.find_by_display_name(params[:display_name])
356
357     if @this_user and
358        (@this_user.visible? or (@user and @user.administrator?))
359       @title = @this_user.display_name
360     else
361       @title = t 'user.no_such_user.title'
362       @not_found_user = params[:display_name]
363       render :action => 'no_such_user', :status => :not_found
364     end
365   end
366
367   def make_friend
368     if params[:display_name]
369       name = params[:display_name]
370       new_friend = User.find_by_display_name(name, :conditions => {:status => ["active", "confirmed"]})
371       friend = Friend.new
372       friend.user_id = @user.id
373       friend.friend_user_id = new_friend.id
374       unless @user.is_friends_with?(new_friend)
375         if friend.save
376           flash[:notice] = t 'user.make_friend.success', :name => name
377           Notifier.deliver_friend_notification(friend)
378         else
379           friend.add_error(t('user.make_friend.failed', :name => name))
380         end
381       else
382         flash[:warning] = t 'user.make_friend.already_a_friend', :name => name
383       end
384
385       if params[:referer]
386         redirect_to params[:referer]
387       else
388         redirect_to :controller => 'user', :action => 'view'
389       end
390     end
391   end
392
393   def remove_friend
394     if params[:display_name]
395       name = params[:display_name]
396       friend = User.find_by_display_name(name, :conditions => {:status => ["active", "confirmed"]})
397       if @user.is_friends_with?(friend)
398         Friend.delete_all "user_id = #{@user.id} AND friend_user_id = #{friend.id}"
399         flash[:notice] = t 'user.remove_friend.success', :name => friend.display_name
400       else
401         flash[:error] = t 'user.remove_friend.not_a_friend', :name => friend.display_name
402       end
403
404       if params[:referer]
405         redirect_to params[:referer]
406       else
407         redirect_to :controller => 'user', :action => 'view'
408       end
409     end
410   end
411
412   ##
413   # sets a user's status
414   def set_status
415     @this_user.update_attributes(:status => params[:status])
416     redirect_to :controller => 'user', :action => 'view', :display_name => params[:display_name]
417   end
418
419   ##
420   # delete a user, marking them as deleted and removing personal data
421   def delete
422     @this_user.delete
423     redirect_to :controller => 'user', :action => 'view', :display_name => params[:display_name]
424   end
425
426   ##
427   # display a list of users matching specified criteria
428   def list
429     if request.post?
430       ids = params[:user].keys.collect { |id| id.to_i }
431
432       User.update_all("status = 'confirmed'", :id => ids) if params[:confirm]
433       User.update_all("status = 'deleted'", :id => ids) if params[:hide]
434
435       redirect_to url_for(:status => params[:status], :ip => params[:ip], :page => params[:page])
436     else
437       conditions = Hash.new
438       conditions[:status] = params[:status] if params[:status]
439       conditions[:creation_ip] = params[:ip] if params[:ip]
440
441       @user_pages, @users = paginate(:users,
442                                      :conditions => conditions,
443                                      :order => :id,
444                                      :per_page => 50)
445     end
446   end
447
448 private
449
450   ##
451   # handle password authentication
452   def password_authentication(username, password)
453     if user = User.authenticate(:username => username, :password => password)
454       successful_login(user)
455     elsif user = User.authenticate(:username => username, :password => password, :pending => true)
456       failed_login t('user.login.account not active', :reconfirm => url_for(:action => 'confirm_resend', :display_name => user.display_name))
457     elsif User.authenticate(:username => username, :password => password, :suspended => true)
458       webmaster = link_to t('user.login.webmaster'), "mailto:webmaster@openstreetmap.org"
459       failed_login t('user.login.account suspended', :webmaster => webmaster)
460     else
461       failed_login t('user.login.auth failure')
462     end
463   end
464
465   ##
466   # handle OpenID authentication
467   def openid_authentication(openid_url)
468     # If we don't appear to have a user for this URL then ask the
469     # provider for some extra information to help with signup
470     if openid_url and User.find_by_openid_url(openid_url)
471       required = nil
472     else
473       required = [:nickname, :email, "http://axschema.org/namePerson/friendly", "http://axschema.org/contact/email"]
474     end
475
476     # Start the authentication
477     authenticate_with_open_id(openid_expand_url(openid_url), :required => required) do |result, identity_url, sreg, ax|
478       if result.successful?
479         # We need to use the openid url passed back from the OpenID provider
480         # rather than the one supplied by the user, as these can be different.
481         #
482         # For example, you can simply enter yahoo.com in the login box rather
483         # than a user specific url. Only once it comes back from the provider
484         # provider do we know the unique address for the user.
485         if user = User.find_by_openid_url(identity_url)
486           case user.status
487             when "pending" then
488               failed_login t('user.login.account not active')
489             when "active", "confirmed" then
490               successful_login(user)
491             when "suspended" then
492               webmaster = link_to t('user.login.webmaster'), "mailto:webmaster@openstreetmap.org"
493               failed_login t('user.login.account suspended', :webmaster => webmaster)
494             else
495               failed_login t('user.login.auth failure')
496           end
497         else
498           # We don't have a user registered to this OpenID, so redirect
499           # to the create account page with username and email filled
500           # in if they have been given by the OpenID provider through
501           # the simple registration protocol.
502           nickname = sreg["nickname"] || ax["http://axschema.org/namePerson/friendly"]
503           email = sreg["email"] || ax["http://axschema.org/contact/email"]
504           redirect_to :controller => 'user', :action => 'new', :nickname => nickname, :email => email, :openid => identity_url
505         end
506       elsif result.missing?
507         failed_login t('user.login.openid missing provider')
508       elsif result.invalid?
509         failed_login t('user.login.openid invalid')
510       else
511         failed_login t('user.login.auth failure')
512       end
513     end
514   end
515
516   ##
517   # verify an OpenID URL
518   def openid_verify(openid_url, user)
519     user.openid_url = openid_url
520
521     authenticate_with_open_id(openid_expand_url(openid_url)) do |result, identity_url|
522       if result.successful?
523         # We need to use the openid url passed back from the OpenID provider
524         # rather than the one supplied by the user, as these can be different.
525         #
526         # For example, you can simply enter yahoo.com in the login box rather
527         # than a user specific url. Only once it comes back from the provider
528         # provider do we know the unique address for the user.
529         user.openid_url = identity_url
530         yield user
531       elsif result.missing?
532         flash.now[:error] = t 'user.login.openid missing provider'
533       elsif result.invalid?
534         flash.now[:error] = t 'user.login.openid invalid'
535       else
536         flash.now[:error] = t 'user.login.auth failure'
537       end
538     end
539   end
540
541   ##
542   # special case some common OpenID providers by applying heuristics to
543   # try and come up with the correct URL based on what the user entered
544   def openid_expand_url(openid_url)
545     if openid_url.nil?
546       return nil
547     elsif openid_url.match(/(.*)gmail.com(\/?)$/) or openid_url.match(/(.*)googlemail.com(\/?)$/)
548       # Special case gmail.com as it is potentially a popular OpenID
549       # provider and, unlike yahoo.com, where it works automatically, Google
550       # have hidden their OpenID endpoint somewhere obscure this making it
551       # somewhat less user friendly.
552       return 'https://www.google.com/accounts/o8/id'
553     else
554       return openid_url
555     end
556   end  
557
558   ##
559   # process a successful login
560   def successful_login(user)
561     session[:user] = user.id
562
563     session_expires_after 1.month if session[:remember_me]
564
565     if user.blocked_on_view
566       redirect_to user.blocked_on_view, :referer => params[:referer]
567     elsif session[:referer]
568       redirect_to session[:referer]
569     else
570       redirect_to :controller => 'site', :action => 'index'
571     end
572
573     session.delete(:remember_me)
574     session.delete(:referer)
575   end
576
577   ##
578   # process a failed login
579   def failed_login(message)
580     flash[:error] = message
581
582     redirect_to :action => 'login', :referer =>  session[:referer]
583
584     session.delete(:remember_me)
585     session.delete(:referer)
586   end
587
588   ##
589   # update a user's details
590   def update_user(user)
591     if user.save
592       set_locale
593
594       if user.new_email.nil? or user.new_email.empty?
595         flash.now[:notice] = t 'user.account.flash update success'
596       else
597         flash.now[:notice] = t 'user.account.flash update success confirm needed'
598
599         begin
600           Notifier.deliver_email_confirm(user, user.tokens.create)
601         rescue
602           # Ignore errors sending email
603         end
604       end
605     end
606   end
607
608   ##
609   # require that the user is a administrator, or fill out a helpful error message
610   # and return them to the user page.
611   def require_administrator
612     if @user and not @user.administrator?
613       flash[:error] = t('user.filter.not_an_administrator')
614
615       if params[:display_name]
616         redirect_to :controller => 'user', :action => 'view', :display_name => params[:display_name]
617       else
618         redirect_to :controller => 'user', :action => 'login', :referer => request.request_uri
619       end
620     elsif not @user
621       redirect_to :controller => 'user', :action => 'login', :referer => request.request_uri
622     end
623   end
624
625   ##
626   # ensure that there is a "this_user" instance variable
627   def lookup_this_user
628     @this_user = User.find_by_display_name(params[:display_name])
629   rescue ActiveRecord::RecordNotFound
630     redirect_to :controller => 'user', :action => 'view', :display_name => params[:display_name] unless @this_user
631   end
632 end