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