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