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