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