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