Changesets can be listed by changeset number.
The Git repository is here.
- Revision:
- 476
- Log:
Various fixes:
* Clear cookies on login
* Warn about cookie use before login
* HTTPS / other parameter improvements for own cookie
- Author:
- rool
- Date:
- Wed Jun 19 05:51:04 +0100 2019
- Size:
- 19754 Bytes
1 | ####################################################################### |
2 | # File: account_controller.rb # |
3 | # (C) Hipposoft 2006-2011 # |
4 | # # |
5 | # Purpose: Hub account management. # |
6 | # # |
7 | # Author: A.D.Hodgkinson # |
8 | # # |
9 | # History: 31-Jan-2011 (ADH): Comment header added; prior history # |
10 | # not recorded. # |
11 | ####################################################################### |
12 | |
13 | class AccountController < ApplicationController |
14 | |
15 | layout 'default.html.erb' |
16 | |
17 | # Cache the logged in and out PNG images in RAM; they're only small. |
18 | |
19 | @@logged_in_image = File.read("#{RAILS_ROOT}/public/images/icons/logged_in.png") |
20 | @@logged_out_image = File.read("#{RAILS_ROOT}/public/images/icons/logged_out.png") |
21 | |
22 | # Action permissions for this class as a class variable, exposed |
23 | # to the public through a class method. |
24 | |
25 | @@hubssolib_permissions = HubSsoLib::Permissions.new({ |
26 | :change_password => [ :admin, :webmaster, :privileged, :normal ], |
27 | :change_details => [ :admin, :webmaster, :privileged, :normal ], |
28 | :delete => [ :admin, :webmaster, :privileged, :normal ], |
29 | :delete_confirm => [ :admin, :webmaster, :privileged, :normal ], |
30 | :list => [ :admin, :webmaster, :privileged ], |
31 | :enumerate => [ :admin, :webmaster ], |
32 | :show => [ :admin, :webmaster ], |
33 | :edit_roles => [ :admin ], |
34 | :destroy => [ :admin ] |
35 | }) |
36 | |
37 | def AccountController.hubssolib_permissions |
38 | @@hubssolib_permissions |
39 | end |
40 | |
41 | # HTTPS enforcement for all methods, except the login indicator; if someone |
42 | # is on an HTTP page, the login indicator needs to be fetched by HTTP too so |
43 | # it can show "logged out" as the secure-only cookies won't get sent. It is |
44 | # very confusing to be on an HTTP page, apparently fetching the indicator by |
45 | # HTTP, only to have the image fetch quietly redirect behind the scenes, go |
46 | # to HTTPS, and say you're logged in - when everyone else thinks you're not. |
47 | |
48 | before_filter :hubssolib_ensure_https, :except => :login_indication |
49 | |
50 | # The "proper" login method |
51 | # |
52 | def login |
53 | @title = 'Log in' |
54 | |
55 | # GET methods just show the login screen. We dump all known application |
56 | # cookies at this point, since they can be stale and logins might not be |
57 | # recognised within those applications otherwise. |
58 | # |
59 | unless request.post? |
60 | cookies.delete( '_canvass_session' ) |
61 | cookies.delete( '_radiantapp_session_id' ) |
62 | cookies.delete( '_instikiapp_session_id' ) |
63 | cookies.delete( 'beastapp_session_id' ) |
64 | cookies.delete( 'typoapp_session_id' ) |
65 | cookies.delete( 'gulleryapp_session_id' ) |
66 | cookies.delete( 'collaboaapp_session_id' ) |
67 | cookies.delete( 'rcvswebapp_session_id' ) |
68 | |
69 | return |
70 | end |
71 | |
72 | @email = params[:email] |
73 | self.hubssolib_current_user = from_real_user(User.authenticate(@email, params[:password])) |
74 | |
75 | if (self.hubssolib_current_user and self.hubssolib_current_user != :false) |
76 | hubssolib_set_last_used(Time.now.utc) |
77 | |
78 | privileges = hubssolib_get_user_roles.to_human_s.downcase |
79 | hubssolib_set_flash( |
80 | :notice, |
81 | "Logged in successfully. Welcome, #{hubssolib_get_user_name}. " << |
82 | "You have #{privileges} privileges." |
83 | ) |
84 | |
85 | hubssolib_redirect_back_or_default(:controller => 'tasks', :action => nil) |
86 | else |
87 | hubssolib_set_flash(:alert, 'Incorrect e-mail address or password.') |
88 | end |
89 | end |
90 | |
91 | # Log out the user and redirect to the Tasks controller. |
92 | # |
93 | def logout |
94 | @title = 'Log out' |
95 | hubssolib_log_out() |
96 | hubssolib_set_flash(:attention, 'You are now logged out.') |
97 | redirect_to :controller => 'tasks', :action => nil |
98 | end |
99 | |
100 | def signup |
101 | @title = 'Sign up' |
102 | return unless request.post? |
103 | |
104 | # Bulk assignment from the params hash is safe because the User object |
105 | # contains nothing that won't be overwritten anyway or isn't already |
106 | # protected by attr_accessible in the User model. |
107 | |
108 | @user = User.new(params[:user]) |
109 | |
110 | if ( @user.email.downcase.ends_with?( '.kr' ) || @user.email.downcase.ends_with?( '.cn' ) ) |
111 | hubssolib_set_flash(:attention, 'Due to overwhelming spam volumes from some locations, self-signups for those locations are blocked. Please contact ROOL for assistence.') |
112 | redirect_to :controller => 'tasks', :action => nil |
113 | return |
114 | end |
115 | |
116 | # Are there any users yet? If not, grant this user admin permissions. |
117 | # Administrators are for just this application; whether or not admin |
118 | # privileges affect other applications depends on the level of external |
119 | # SSO integration. |
120 | |
121 | if (User.count.zero?) |
122 | |
123 | @user.roles = HubSsoLib::Roles.new(true).to_s |
124 | @user.save! |
125 | @user.activate |
126 | self.hubssolib_current_user = from_real_user(@user) |
127 | |
128 | hubssolib_set_flash( |
129 | :notice, |
130 | 'Thanks for signing up. You are now the system administrator ' << |
131 | 'and your account has been automatically activated.' |
132 | ) |
133 | |
134 | else |
135 | |
136 | # Have to do the captcha verification explicitly before trying to save |
137 | # the model, as verification is not done as part of validation and we |
138 | # don't want to successfully save a model only to find that the captcha |
139 | # text is incorrect. |
140 | |
141 | begin |
142 | success = verify_recaptcha( |
143 | :model => @user, |
144 | :message => "The reCaptcha challenge wasn't happy with the response. Please try again or contact RISC OS Open for assistance." |
145 | ) |
146 | rescue => e |
147 | Rails.logger.error(e.inspect) |
148 | hubssolib_set_flash(:attention, 'The reCaptcha system is currently not available and cannot verify signups. Please try again later.') |
149 | redirect_to :controller => 'tasks', :action => nil |
150 | return |
151 | end |
152 | |
153 | raise ActiveRecord::RecordInvalid.new(@user) if not success |
154 | |
155 | @user.roles = HubSsoLib::Roles.new(false).to_s |
156 | @user.save! |
157 | |
158 | hubssolib_set_flash( |
159 | :notice, |
160 | 'Thanks for signing up. Your site account must be activated ' << |
161 | 'before you can use it - please check your e-mail account ' << |
162 | 'for a message which tells you what you should do next.' |
163 | ) |
164 | |
165 | end |
166 | |
167 | redirect_to :controller => 'tasks', :action => nil |
168 | |
169 | rescue ActiveRecord::RecordInvalid |
170 | render :action => 'signup' |
171 | end |
172 | |
173 | def activate |
174 | activation_code = params[:activation_code] || params[:id] |
175 | |
176 | unless activation_code.nil? |
177 | @user = User.find_by_activation_code(activation_code) |
178 | |
179 | if @user and @user.activate |
180 | self.hubssolib_current_user = from_real_user(@user) |
181 | |
182 | hubssolib_set_flash( |
183 | :notice, |
184 | "Your #{INSTITUTION_NAME_LONG} web site account is now active." |
185 | ) |
186 | |
187 | hubssolib_redirect_back_or_default(:controller => 'tasks', :action => nil) |
188 | else |
189 | hubssolib_set_flash( |
190 | :alert, |
191 | "Unable to activate your #{INSTITUTION_NAME_LONG} web site account. " << |
192 | 'Is the activation code correct, or has it already been used? ' << |
193 | "If in doubt please try to sign up again. Contact #{INSTITUTION_NAME_SHORT} if you " << |
194 | 'keep having trouble.' |
195 | ) |
196 | |
197 | redirect_to :controller => 'account', :action => 'signup' |
198 | end |
199 | else |
200 | redirect_to :controller => 'account', :action => 'signup' |
201 | end |
202 | end |
203 | |
204 | def change_password |
205 | @title = 'Change password' |
206 | return unless request.post? |
207 | |
208 | user = to_real_user(self.hubssolib_current_user) |
209 | |
210 | if User.authenticate(user.email, params[:old_password]) |
211 | if (params[:password] == params[:password_confirmation]) |
212 | user.password_confirmation = params[:password_confirmation] |
213 | user.password = params[:password] |
214 | save_password_and_set_flash(user) |
215 | self.hubssolib_current_user = from_real_user(user) |
216 | |
217 | redirect_to :controller => 'tasks', :action => nil |
218 | else |
219 | set_password_mismatch_flash |
220 | @old_password = params[:old_password] |
221 | end |
222 | else |
223 | hubssolib_set_flash(:alert, 'Incorrect current password.') |
224 | end |
225 | end |
226 | |
227 | def change_details |
228 | @title = 'Update account details' |
229 | @user = to_real_user(self.hubssolib_current_user) |
230 | @real_name = @user ? @user.real_name || '' : '' |
231 | |
232 | return unless request.post? |
233 | |
234 | if (params[:real_name]) |
235 | @user.real_name = @real_name = params[:real_name] |
236 | @user.save! |
237 | self.hubssolib_current_user = from_real_user(@user) |
238 | |
239 | hubssolib_set_flash(:notice, 'Account details updated successfully.') |
240 | redirect_to :controller => 'tasks', :action => nil |
241 | end |
242 | end |
243 | |
244 | def forgot_password |
245 | @title = 'Forgotten password' |
246 | return unless request.post? |
247 | |
248 | @user = User.find(:first, :conditions => ["LOWER(email) = ?", params[:email].downcase]) |
249 | |
250 | unless @user.nil? |
251 | @user.forgot_password |
252 | @user.save! |
253 | |
254 | hubssolib_set_flash( |
255 | :notice, |
256 | 'An e-mail message which tells you how to reset your ' << |
257 | 'account password has been set to your e-mail address.' |
258 | ) |
259 | |
260 | redirect_to :controller => 'tasks', :action => nil |
261 | else |
262 | hubssolib_set_flash( |
263 | :alert, |
264 | 'No account was found for the given e-mail address.' |
265 | ) |
266 | end |
267 | end |
268 | |
269 | def reset_password |
270 | @title = 'Reset password' |
271 | |
272 | if params[:id].nil? |
273 | hubssolib_redirect_back_or_default(:controller => 'tasks', :action => nil) |
274 | return |
275 | end |
276 | |
277 | @user = User.find_by_password_reset_code(params[:id]) |
278 | |
279 | if (@user.nil?) |
280 | hubssolib_set_flash( |
281 | :alert, |
282 | 'Invalid reset code. Did your e-mail client break up the reset ' << |
283 | 'link so it spanned more than one line? If so, please try again, ' << |
284 | 'copying all of the link in the message however many lines it spans.' |
285 | ) |
286 | |
287 | hubssolib_redirect_back_or_default(:controller => 'tasks', :action => nil) |
288 | return |
289 | end |
290 | |
291 | t = Time.now.utc |
292 | if (t >= (@user.password_reset_code_expires_at || t)) # Allows for 'nil' in expiry field |
293 | hubssolib_set_flash( |
294 | :alert, |
295 | 'The reset code has expired. Please try your reset request again.' |
296 | ) |
297 | redirect_to :controller => 'account', :action => 'forgot_password' |
298 | return |
299 | end |
300 | |
301 | unless params[:password] |
302 | hubssolib_set_flash(:alert, 'Reset your password using the form below.') |
303 | return |
304 | end |
305 | |
306 | if (params[:password] == params[:password_confirmation]) |
307 | @user.password_confirmation = params[:password_confirmation] |
308 | @user.password = params[:password] |
309 | @user.reset_password |
310 | save_password_and_set_flash(@user) |
311 | self.hubssolib_current_user = from_real_user(@user) |
312 | redirect_to :controller => 'tasks', :action => nil |
313 | return |
314 | else |
315 | set_password_mismatch_flash |
316 | return |
317 | end |
318 | end |
319 | |
320 | def delete |
321 | hubssolib_set_flash(:alert, 'Are you sure?') |
322 | title = 'Delete account: Are you sure?' |
323 | end |
324 | |
325 | def delete_confirm |
326 | me = to_real_user(self.hubssolib_current_user) |
327 | hubssolib_log_out() |
328 | me.destroy |
329 | |
330 | hubssolib_clear_flash() |
331 | hubssolib_set_flash(:attention, 'Your account has been deleted.') |
332 | redirect_to :controller => 'tasks', :action => nil |
333 | end |
334 | |
335 | def list |
336 | @title = 'List of user accounts' |
337 | |
338 | # Page zero means 'all'. |
339 | |
340 | if (params.has_key?(:page) && params[:page] == '0' ) |
341 | page = 1 |
342 | per_page = User.count |
343 | else |
344 | page = params[:page] |
345 | per_page = 20 |
346 | end |
347 | |
348 | @users = User.paginate :page => page, |
349 | :per_page => per_page, |
350 | :order => 'created_at DESC' |
351 | end |
352 | |
353 | # Enumerate active users (those users known to the DRb server). |
354 | # |
355 | def enumerate |
356 | @title = 'Active users' |
357 | @users = hubssolib_enumerate_users |
358 | @users = [] if @users.nil? |
359 | |
360 | # Map the user objects returned from the HubSsoLib Gem to |
361 | # internal users. |
362 | |
363 | @users.map! { |user| to_real_user(user) } |
364 | |
365 | # Page number zero is magic; it indicates "all items". |
366 | |
367 | if (params.has_key?(:page) && params[:page] == '0' ) |
368 | page = 1 |
369 | per_page = @users.count |
370 | else |
371 | page = params[:page] |
372 | per_page = 20 |
373 | end |
374 | |
375 | @users = @users.paginate :page => page, |
376 | :per_page => per_page, |
377 | :order => 'created_at DESC' |
378 | end |
379 | |
380 | # Show details of a specific user account. |
381 | # |
382 | def show |
383 | @title = 'User account details' |
384 | @user = User.find(params[:id]) |
385 | @referrer = request.env["HTTP_REFERER"] |
386 | @referrer = nil unless (@referrer && !@referrer.empty?) |
387 | end |
388 | |
389 | def edit_roles |
390 | @title = 'Edit account roles' |
391 | |
392 | # We must have a valid ID |
393 | |
394 | unless (request.post?) and (params[:id]) and (@user = User.find(params[:id])) |
395 | redirect_to :controller => 'tasks', :action => nil |
396 | return |
397 | end |
398 | |
399 | # If 'commit' is present, the form was submitted with details rather than |
400 | # visited from a list or account details view. |
401 | |
402 | return unless (params[:commit]) |
403 | |
404 | # Validate the result |
405 | |
406 | roles = (params[:user] ? params[:user][:roles] : '').to_authenticated_roles |
407 | |
408 | unless (roles.validate) |
409 | hubssolib_set_flash( |
410 | :alert, |
411 | 'Invalid roles chosen. ' << |
412 | 'At least one item in the list must be selected.' |
413 | ) |
414 | else |
415 | @user.roles = roles.to_s |
416 | @user.save! |
417 | |
418 | # Did I update my own roles? |
419 | |
420 | if (hubssolib_get_user_id == @user.id) |
421 | self.hubssolib_current_user = from_real_user(@user) |
422 | end |
423 | |
424 | hubssolib_set_flash(:notice, 'Account roles updated successfully.') |
425 | redirect_to :action => 'show', :id => @user.id |
426 | end |
427 | end |
428 | |
429 | def destroy |
430 | user = User.find(params[:id]) |
431 | |
432 | if (hubssolib_get_user_id == user.id) |
433 | hubssolib_set_flash( |
434 | :alert, |
435 | 'Please use the normal control panel below to delete your own account.' |
436 | ) |
437 | redirect_to root_path |
438 | return |
439 | elsif (user.roles.to_authenticated_roles.include?(:admin)) |
440 | hubssolib_set_flash( |
441 | :alert, |
442 | 'You cannot destroy an administrator account from here! ' << |
443 | 'You can only do that at the control panel when ' << |
444 | 'logged into the account, or at the database level.' |
445 | ) |
446 | else |
447 | user.destroy |
448 | hubssolib_set_flash(:alert, 'The account has been deleted.') |
449 | end |
450 | |
451 | redirect_to :action => 'list' |
452 | end |
453 | |
454 | # The login_indication method is unusual; it returns data for an image, |
455 | # with no-cache parameters set, to indicate whether or not the user is |
456 | # logged in. It does not render a view. |
457 | # |
458 | # The idea is that a caller which caches HTML can include an image tag |
459 | # that points its source data to this method; the image will be updated |
460 | # even if the HTML stays cached. |
461 | # |
462 | def login_indication |
463 | headers['Pragma'] = 'no-cache' |
464 | headers['Cache-Control'] = 'no-cache, must-revalidate' |
465 | |
466 | send_data hubssolib_logged_in? ? @@logged_in_image : @@logged_out_image, |
467 | :type => 'image/png', |
468 | :disposition => 'inline' |
469 | end |
470 | |
471 | # A supporting unusual method is login_conditional, which redirects to |
472 | # the login page if the user is logged out or the tasks page if the user |
473 | # is logged in. It explicitly clears a return-to link, if there is one, |
474 | # so that the user doesn't drop out of Hub. This is useful if the page |
475 | # from which the user came cannot support (for example) the Flash display |
476 | # because of, say, caching. |
477 | # |
478 | def login_conditional |
479 | if (hubssolib_ensure_https) # Redirect back to here using HTTPS, if not already |
480 | hubssolib_store_location(nil) |
481 | |
482 | if (hubssolib_logged_in?) |
483 | redirect_to :controller => 'tasks', :action => nil |
484 | else |
485 | redirect_to :action => 'login' |
486 | end |
487 | end |
488 | end |
489 | |
490 | protected |
491 | |
492 | # Pass a HubSsoLib::User object. Returns an equivalent User Model object. |
493 | # |
494 | def to_real_user(user) |
495 | return nil if user.nil? |
496 | raise 'Incorrect argument class' unless (user.class == HubSsoLib::User or user.class == DRbObject) |
497 | |
498 | # Unpleasant "user_" prefix in HubSsoLib::User field names is to avoid |
499 | # collisions (e.g. of "id") with DRbObject. |
500 | |
501 | real_user = User.find(user.user_id) |
502 | raise 'No equivalent real user' unless real_user |
503 | |
504 | real_user.activated_at = Time.zone.parse(user.user_activated_at) |
505 | real_user.activation_code = user.user_activation_code |
506 | real_user.created_at = Time.zone.parse(user.user_created_at) |
507 | real_user.crypted_password = user.user_crypted_password |
508 | real_user.email = user.user_email |
509 | real_user.member_id = user.user_member_id |
510 | real_user.password_reset_code = user.user_password_reset_code |
511 | real_user.password_reset_code_expires_at = Time.zone.parse(user.user_password_reset_code_expires_at) |
512 | real_user.real_name = user.user_real_name |
513 | real_user.remember_token = user.user_remember_token |
514 | real_user.remember_token_expires_at = Time.zone.parse(user.user_remember_token_expires_at) |
515 | real_user.roles = user.user_roles |
516 | real_user.salt = user.user_salt |
517 | real_user.updated_at = Time.zone.parse(user.user_updated_at) |
518 | |
519 | return real_user |
520 | end |
521 | |
522 | # Pass a User Model object. Returns an equivalent HubSsoLib::User object. |
523 | # |
524 | def from_real_user(real_user) |
525 | return nil if real_user.nil? |
526 | raise 'Incorrect argument class' unless real_user.class == User |
527 | |
528 | user = HubSsoLib::User.new |
529 | |
530 | user.user_activated_at = real_user.activated_at.to_s |
531 | user.user_activation_code = real_user.activation_code |
532 | user.user_created_at = real_user.created_at.to_s |
533 | user.user_crypted_password = real_user.crypted_password |
534 | user.user_email = real_user.email |
535 | user.user_id = real_user.id |
536 | user.user_member_id = real_user.member_id |
537 | user.user_password_reset_code = real_user.password_reset_code |
538 | user.user_password_reset_code_expires_at = real_user.password_reset_code_expires_at.to_s |
539 | user.user_real_name = real_user.real_name |
540 | user.user_remember_token = real_user.remember_token |
541 | user.user_remember_token_expires_at = real_user.remember_token_expires_at.to_s |
542 | user.user_roles = real_user.roles |
543 | user.user_salt = real_user.salt |
544 | user.user_updated_at = real_user.updated_at.to_s |
545 | |
546 | return user |
547 | end |
548 | |
549 | def save_password_and_set_flash(user) |
550 | if ( user.save ) |
551 | hubssolib_set_flash(:notice, 'Your password has been changed.') |
552 | else |
553 | hubssolib_set_flash(:alert, 'Sorry, your password could not be changed.') |
554 | end |
555 | end |
556 | |
557 | def set_password_mismatch_flash |
558 | hubssolib_set_flash( |
559 | :alert, |
560 | 'The new password differed from the password confirmation you entered.' |
561 | ) |
562 | end |
563 | end |