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