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