Fixed some failing tests caused by the openID changes
[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 => [:set_home, :account, :go_public, :make_friend, :remove_friend, :upload_image, :delete_image]
8   before_filter :check_database_readable, :except => [:api_details, :api_gpx_files]
9   before_filter :check_database_writable, :only => [:login, :new, :set_home, :account, :go_public, :make_friend, :remove_friend, :upload_image, :delete_image]
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 => [:activate, :deactivate, :hide, :unhide, :delete]
15   before_filter :lookup_this_user, :only => [:activate, :deactivate, :hide, :unhide, :delete]
16
17   filter_parameter_logging :password, :pass_crypt, :pass_crypt_confirmation
18   
19   def save
20     @title = t 'user.new.title'
21
22     if Acl.find_by_address(request.remote_ip, :conditions => {:k => "no_account_creation"})
23       render :action => 'new'
24     else
25           #The redirect from the OpenID provider reenters here again
26           #and we need to pass the parameters through to the 
27           #open_id_authentication function a second time
28           if params[:open_id_complete]
29                 openid_verify('', true)
30                 #We have set the user.openid_url to nil beforehand. If it hasn't
31                 #been set to a new valid openid_url, it means the openid couldn't be validated
32                 if @user.nil? or @user.openid_url.nil?
33                   render :action => 'new'
34                   return
35                 end  
36           else
37                 @user = User.new(params[:user])
38                 
39                 @user.visible = true
40                 @user.data_public = true
41                 @user.description = "" if @user.description.nil?
42                 @user.creation_ip = request.remote_ip
43                 @user.languages = request.user_preferred_languages
44                 #Set the openid_url to nil as for one it is used
45                 #to check if the openid could be validated and secondly
46                 #to not get dupplicate conflicts for an empty openid 
47                 @user.openid_url = nil
48
49                 if (!params[:user][:openid_url].nil? and params[:user][:openid_url].length > 0)
50                   if @user.pass_crypt.length == 0 
51             #if the password is empty, but we have a openid 
52             #then generate a random passowrd to disable 
53             #loging in via password 
54             @user.pass_crypt = ActiveSupport::SecureRandom.base64(16) 
55             @user.pass_crypt_confirmation = @user.pass_crypt 
56           end
57                   #Validate all of the other fields before
58                   #redirecting to the openid provider
59                   if !@user.valid?
60                         render :action => 'new'
61                   else            
62                         #TODO: Is it a problem to store the user variable with respect to password safty in the session variables?
63                         #Store the user variable in the session for it to be accessible when redirecting back from the openid provider
64                         session[:new_usr] = @user
65                         begin
66                           @norm_openid_url = OpenIdAuthentication.normalize_identifier(params[:user][:openid_url])
67                         rescue
68                           flash.now[:error] = t 'user.login.openid invalid'
69                           render :action => 'new'
70                           return
71                         end
72                         #Verify that the openid provided is valid and that the user is the owner of the id
73                         openid_verify(@norm_openid_url, true)
74                         #openid_verify can return in two ways:
75                         #Either it returns with a redirect to the openid provider who then freshly
76                         #redirects back to this url if the openid is valid, or if the openid is not plausible
77                         #and no provider for it could be found it just returns
78                         #we want to just let the redirect through
79                         if response.headers["Location"].nil?
80                           render :action => 'new'
81                         end
82                   end
83                   #At this point there was either an error and the page has been rendered,
84                   #or there is a redirect to the openid provider and the rest of the method
85                   #gets executed whenn this method gets reentered after redirecting back
86                   #from the openid provider
87                   return
88                 end
89           end
90           if @user.save
91                 flash[:notice] = t 'user.new.flash create success message'
92                 Notifier.deliver_signup_confirm(@user, @user.tokens.create(:referer => params[:referer]))
93                 redirect_to :action => 'login'
94           else
95                 render :action => 'new'
96           end
97     end
98   end
99
100   def account
101     @title = t 'user.account.title'
102     @tokens = @user.oauth_tokens.find :all, :conditions => 'oauth_tokens.invalidated_at is null and oauth_tokens.authorized_at is not null'
103
104     #The redirect from the OpenID provider reenters here again
105     #and we need to pass the parameters through to the 
106     #open_id_authentication function
107     if params[:open_id_complete]
108       openid_verify('', false)
109           @user.save
110       return
111     end
112
113     if params[:user] and params[:user][:display_name] and params[:user][:description]
114       if params[:user][:email] != @user.email
115         @user.new_email = params[:user][:email]
116       end
117
118       @user.display_name = params[:user][:display_name]
119
120       if params[:user][:pass_crypt].length > 0 or params[:user][:pass_crypt_confirmation].length > 0
121         @user.pass_crypt = params[:user][:pass_crypt]
122         @user.pass_crypt_confirmation = params[:user][:pass_crypt_confirmation]
123       end
124       if (params[:user][:openid_url].length == 0)
125         #Clearing doesn't need OpenID validation, so we can set it here.
126         @user.openid_url = nil
127       end
128
129       @user.description = params[:user][:description]
130       @user.languages = params[:user][:languages].split(",")
131       @user.home_lat = params[:user][:home_lat]
132       @user.home_lon = params[:user][:home_lon]
133
134       if @user.save
135         set_locale
136
137         if params[:user][:email] == @user.new_email
138           flash.now[:notice] = t 'user.account.flash update success confirm needed'
139           Notifier.deliver_email_confirm(@user, @user.tokens.create)
140         else
141           flash.now[:notice] = t 'user.account.flash update success'
142         end
143       end
144
145       if (params[:user][:openid_url].length > 0)
146                 begin
147                   @norm_openid_url = OpenIdAuthentication.normalize_identifier(params[:user][:openid_url])
148                   if (@norm_openid_url != @user.openid_url)
149                         #If the OpenID has changed, we want to check that it is a valid OpenID and one
150                         #the user has control over before saving the openID as a password equivalent for
151                         #the user.
152                         openid_verify(@norm_openid_url, false)
153                   end
154                 rescue
155                   flash.now[:error] = t 'user.login.openid invalid'
156                 end
157       end
158
159     end
160   end
161
162   def openid_specialcase_mapping(openid_url)
163     #Special case gmail.com, as it is pontentially a popular OpenID provider and unlike
164     #yahoo.com, where it works automatically, Google have hidden their OpenID endpoint
165     #somewhere obscure making it less userfriendly.
166     if (openid_url.match(/(.*)gmail.com(\/?)$/) or openid_url.match(/(.*)googlemail.com(\/?)$/) )
167       return 'https://www.google.com/accounts/o8/id'
168     end
169
170     return nil
171   end  
172
173   def openid_verify(openid_url,account_create)
174     authenticate_with_open_id(openid_url) do |result, identity_url|
175       if result.successful?
176         #We need to use the openid url passed back from the OpenID provider
177         #rather than the one supplied by the user, as these can be different.
178         #e.g. one can simply enter yahoo.com in the login box, i.e. no user specific url
179         #only once it comes back from the OpenID provider do we know the unique address for
180         #the user.
181                 @user = session[:new_usr] unless @user #this is used for account creation when the user is not yet in the database
182         @user.openid_url = identity_url
183           elsif result.missing?
184                 mapped_id = openid_specialcase_mapping(openid_url)
185                 if mapped_id
186                   openid_verify(mapped_id, account_create)
187                 else
188                   flash.now[:error] = t 'user.login.openid missing provider'
189                 end
190           elsif result.invalid?
191                 flash.now[:error] = t 'user.login.openid invalid'
192           else
193                 flash.now[:error] = t 'user.login.auth failure'
194           end
195     end
196   end
197
198
199   def set_home
200     if params[:user][:home_lat] and params[:user][:home_lon]
201       @user.home_lat = params[:user][:home_lat].to_f
202       @user.home_lon = params[:user][:home_lon].to_f
203       if @user.save
204         flash[:notice] = t 'user.set_home.flash success'
205         redirect_to :controller => 'user', :action => 'account'
206       end
207     end
208   end
209
210   def go_public
211     @user.data_public = true
212     @user.save
213     flash[:notice] = t 'user.go_public.flash success'
214     redirect_to :controller => 'user', :action => 'account', :display_name => @user.display_name
215   end
216
217   def lost_password
218     @title = t 'user.lost_password.title'
219
220     if params[:user] and params[:user][:email]
221       user = User.find_by_email(params[:user][:email], :conditions => {:visible => true})
222
223       if user
224         token = user.tokens.create
225         Notifier.deliver_lost_password(user, token)
226         flash.now[:notice] = t 'user.lost_password.notice email on way'
227       else
228         flash.now[:error] = t 'user.lost_password.notice email cannot find'
229       end
230     end
231   end
232
233   def reset_password
234     @title = t 'user.reset_password.title'
235
236     if params[:token]
237       token = UserToken.find_by_token(params[:token])
238
239       if token
240         @user = token.user
241
242         if params[:user]
243           @user.pass_crypt = params[:user][:pass_crypt]
244           @user.pass_crypt_confirmation = params[:user][:pass_crypt_confirmation]
245           @user.active = true
246           @user.email_valid = true
247
248           if @user.save
249             token.destroy
250             flash[:notice] = t 'user.reset_password.flash changed'
251             redirect_to :action => 'login'
252           end
253         end
254       else
255         flash[:error] = t 'user.reset_password.flash token bad'
256         redirect_to :action => 'lost_password'
257       end
258     end
259   end
260
261   def new
262     @title = t 'user.new.title'
263
264     # The user is logged in already, so don't show them the signup page, instead
265     # send them to the home page
266     redirect_to :controller => 'site', :action => 'index' if session[:user]
267
268     @nickname = params['nickname']
269     @email = params['email']
270         @openID = params['openid']
271   end
272
273   def login
274
275     #The redirect from the OpenID provider reenters here again
276     #and we need to pass the parameters through to the 
277     # open_id_authentication function
278     if params[:open_id_complete]
279       open_id_authentication('')
280     end
281
282     
283     if params[:user] and session[:user].nil?
284
285       if !params[:user][:openid_url].nil? and !params[:user][:openid_url].empty?
286         open_id_authentication(params[:user][:openid_url])
287       else
288         email_or_display_name = params[:user][:email]
289         pass = params[:user][:password]
290         user = User.authenticate(:username => email_or_display_name, :password => pass)
291         if user
292           session[:user] = user.id
293         elsif User.authenticate(:username => email_or_display_name, :password => pass, :inactive => true)
294           flash.now[:error] = t 'user.login.account not active'
295         else
296           flash.now[:error] = t 'user.login.auth failure'
297         end
298       end
299     end
300   
301     if session[:user]
302       # The user is logged in, if the referer param exists, redirect them to that
303       # unless they've also got a block on them, in which case redirect them to
304       # the block so they can clear it.
305       user = User.find(session[:user])
306       block = user.blocked_on_view
307       if block
308         redirect_to block, :referrer => params[:referrer]
309       elsif params[:referer]
310         redirect_to params[:referer]
311       else
312         redirect_to :controller => 'site', :action => 'index'
313       end
314       return
315     end
316
317     @title = t 'user.login.title'
318   end
319
320   def open_id_authentication(openid_url)
321     #TODO: only ask for nickname and email, if we don't already have a user for that openID, in which case
322     #email and nickname are already filled out. I don't know how to do that with ruby syntax though, as we
323     #don't want to duplicate the do block
324     #On the other hand it also doesn't matter too much if we ask every time, as the OpenID provider should
325     #remember these results, and shouldn't repromt the user for these data each time.
326     authenticate_with_open_id(openid_url, :return_to => request.protocol + request.host_with_port + '/login?referer=' + params[:referer], :optional => [:nickname, :email]) do |result, identity_url, registration|
327       if result.successful?
328         #We need to use the openid url passed back from the OpenID provider
329         #rather than the one supplied by the user, as these can be different.
330         #e.g. one can simply enter yahoo.com in the login box, i.e. no user specific url
331         #only once it comes back from the OpenID provider do we know the unique address for
332         #the user.
333         user = User.find_by_openid_url(identity_url)
334         if user
335           if user.visible? and user.active?
336             session[:user] = user.id
337           else
338             user = nil
339             flash.now[:error] = t 'user.login.account not active'
340           end
341         else
342           #We don't have a user registered to this OpenID. Redirect to the create account page
343           #with username and email filled in if they have been given by the OpenID provider through
344           #the simple registration protocol
345           redirect_to :controller => 'user', :action => 'new', :nickname => registration['nickname'], :email => registration['email'], :openid => identity_url
346         end
347       else if result.missing?
348              #Try and apply some heuristics to make common cases more userfriendly
349              mapped_id = openid_specialcase_mapping(openid_url)
350              if mapped_id
351                open_id_authentication(mapped_id)
352              else
353                flash.now[:error] = t 'user.login.openid missing provider'
354              end
355            else if result.invalid?
356                   flash.now[:error] = t 'user.login.openid invalid'
357                 else
358                   flash.now[:error] = t 'user.login.auth failure'
359                 end
360            end
361       end
362     end
363   end
364
365   def logout
366     if session[:token]
367       token = UserToken.find_by_token(session[:token])
368       if token
369         token.destroy
370       end
371       session[:token] = nil
372     end
373     session[:user] = nil
374     if params[:referer]
375       redirect_to params[:referer]
376     else
377       redirect_to :controller => 'site', :action => 'index'
378     end
379   end
380
381   def confirm
382     if params[:confirm_action]
383       token = UserToken.find_by_token(params[:confirm_string])
384       if token and !token.user.active?
385         @user = token.user
386         @user.active = true
387         @user.email_valid = true
388         @user.save!
389         referer = token.referer
390         token.destroy
391         flash[:notice] = t 'user.confirm.success'
392         session[:user] = @user.id
393         unless referer.nil?
394           redirect_to referer
395         else
396           redirect_to :action => 'account', :display_name => @user.display_name
397         end
398       else
399         flash.now[:error] = t 'user.confirm.failure'
400       end
401     end
402   end
403
404   def confirm_email
405     if params[:confirm_action]
406       token = UserToken.find_by_token(params[:confirm_string])
407       if token and token.user.new_email?
408         @user = token.user
409         @user.email = @user.new_email
410         @user.new_email = nil
411         @user.active = true
412         @user.email_valid = true
413         @user.save!
414         token.destroy
415         flash[:notice] = t 'user.confirm_email.success'
416         session[:user] = @user.id
417         redirect_to :action => 'account', :display_name => @user.display_name
418       else
419         flash.now[:error] = t 'user.confirm_email.failure'
420       end
421     end
422   end
423
424   def upload_image
425     @user.image = params[:user][:image]
426     @user.save!
427     redirect_to :controller => 'user', :action => 'view', :display_name => @user.display_name
428   end
429
430   def delete_image
431     @user.image = nil
432     @user.save!
433     redirect_to :controller => 'user', :action => 'view', :display_name => @user.display_name
434   end
435
436   def api_gpx_files
437     doc = OSM::API.new.get_xml_doc
438     @user.traces.each do |trace|
439       doc.root << trace.to_xml_node() if trace.public? or trace.user == @user
440     end
441     render :text => doc.to_s, :content_type => "text/xml"
442   end
443
444   def view
445     @this_user = User.find_by_display_name(params[:display_name])
446
447     if @this_user and
448        (@this_user.visible? or (@user and @user.administrator?))
449       @title = @this_user.display_name
450     else
451       @title = t 'user.no_such_user.title'
452       @not_found_user = params[:display_name]
453       render :action => 'no_such_user', :status => :not_found
454     end
455   end
456
457   def make_friend
458     if params[:display_name]
459       name = params[:display_name]
460       new_friend = User.find_by_display_name(name, :conditions => {:visible => true})
461       friend = Friend.new
462       friend.user_id = @user.id
463       friend.friend_user_id = new_friend.id
464       unless @user.is_friends_with?(new_friend)
465         if friend.save
466           flash[:notice] = t 'user.make_friend.success', :name => name
467           Notifier.deliver_friend_notification(friend)
468         else
469           friend.add_error(t('user.make_friend.failed', :name => name))
470         end
471       else
472         flash[:warning] = t 'user.make_friend.already_a_friend', :name => name
473       end
474
475       redirect_to :controller => 'user', :action => 'view'
476     end
477   end
478
479   def remove_friend
480     if params[:display_name]
481       name = params[:display_name]
482       friend = User.find_by_display_name(name, :conditions => {:visible => true})
483       if @user.is_friends_with?(friend)
484         Friend.delete_all "user_id = #{@user.id} AND friend_user_id = #{friend.id}"
485         flash[:notice] = t 'user.remove_friend.success', :name => friend.display_name
486       else
487         flash[:error] = t 'user.remove_friend.not_a_friend', :name => friend.display_name
488       end
489
490       redirect_to :controller => 'user', :action => 'view'
491     end
492   end
493
494   ##
495   # activate a user, allowing them to log in
496   def activate
497     @this_user.update_attributes(:active => true)
498     redirect_to :controller => 'user', :action => 'view', :display_name => params[:display_name]
499   end
500
501   ##
502   # deactivate a user, preventing them from logging in
503   def deactivate
504     @this_user.update_attributes(:active => false)
505     redirect_to :controller => 'user', :action => 'view', :display_name => params[:display_name]
506   end
507
508   ##
509   # hide a user, marking them as logically deleted
510   def hide
511     @this_user.update_attributes(:visible => false)
512     redirect_to :controller => 'user', :action => 'view', :display_name => params[:display_name]
513   end
514
515   ##
516   # unhide a user, clearing the logically deleted flag
517   def unhide
518     @this_user.update_attributes(:visible => true)
519     redirect_to :controller => 'user', :action => 'view', :display_name => params[:display_name]
520   end
521
522   ##
523   # delete a user, marking them as deleted and removing personal data
524   def delete
525     @this_user.delete
526     redirect_to :controller => 'user', :action => 'view', :display_name => params[:display_name]
527   end
528 private
529   ##
530   # require that the user is a administrator, or fill out a helpful error message
531   # and return them to the user page.
532   def require_administrator
533     unless @user.administrator?
534       flash[:error] = t('user.filter.not_an_administrator')
535       redirect_to :controller => 'user', :action => 'view', :display_name => params[:display_name]
536     end
537   end
538
539   ##
540   # ensure that there is a "this_user" instance variable
541   def lookup_this_user
542     @this_user = User.find_by_display_name(params[:display_name])
543   rescue ActiveRecord::RecordNotFound
544     redirect_to :controller => 'user', :action => 'view', :display_name => params[:display_name] unless @this_user
545   end
546 end