blib/lib/CAM/UserApp.pm | |||
---|---|---|---|
Criterion | Covered | Total | % |
statement | 7 | 9 | 77.7 |
branch | n/a | ||
condition | n/a | ||
subroutine | 3 | 3 | 100.0 |
pod | n/a | ||
total | 10 | 12 | 83.3 |
line | stmt | bran | cond | sub | pod | time | code |
---|---|---|---|---|---|---|---|
1 | package CAM::UserApp; | ||||||
2 | |||||||
3 | =head1 NAME | ||||||
4 | |||||||
5 | CAM::UserApp - Extension of CAM::App to support web login | ||||||
6 | |||||||
7 | =head1 LICENSE | ||||||
8 | |||||||
9 | Copyright 2005 Clotho Advanced Media, Inc., |
||||||
10 | |||||||
11 | This library is free software; you can redistribute it and/or modify it | ||||||
12 | under the same terms as Perl itself. | ||||||
13 | |||||||
14 | =head1 DESCRIPTION | ||||||
15 | |||||||
16 | CAM::UserApp provides generic session-based login capabilities. It | ||||||
17 | supports login, state maintenance and password changing in a framework | ||||||
18 | that supports either SOAP or cookie-based HTML, among other | ||||||
19 | possibilities. | ||||||
20 | |||||||
21 | CAM::UserApp is not complete by itself. Some of its methods must be | ||||||
22 | implemented by a subclass. In particular, retrieveUser() must be | ||||||
23 | supplied. In an HTML or other human-interaction environment, the | ||||||
24 | offerLogin() and offerChangePassword() methods should be implemented. | ||||||
25 | Others are optional, and are described below. | ||||||
26 | |||||||
27 | =head1 SYNOPSIS | ||||||
28 | |||||||
29 | A nearly-complete example subclass: | ||||||
30 | |||||||
31 | package MyApp; | ||||||
32 | use CAM::UserApp; | ||||||
33 | our @ISA=qw(CAM::UserApp); | ||||||
34 | |||||||
35 | sub retrieveUser { | ||||||
36 | my ($self, $user, $pass) = @_; | ||||||
37 | # (do some SQL lookup perhaps) | ||||||
38 | my $user = Some::Pkg->new($user, $pass); | ||||||
39 | return $user; | ||||||
40 | } | ||||||
41 | |||||||
42 | sub offerLogin { | ||||||
43 | my ($self, %args) = @_; | ||||||
44 | print $self->header(); | ||||||
45 | $self->getTemplate("login.tmpl", | ||||||
46 | error=>$args{error}, | ||||||
47 | passthru=>$args{passthru}) | ||||||
48 | ->print(); | ||||||
49 | } | ||||||
50 | |||||||
51 | sub offerChangePassword { | ||||||
52 | my ($self, %args) = @_; | ||||||
53 | print $self->header(); | ||||||
54 | $self->getTemplate("changePass.tmpl", error=>$args{error}) | ||||||
55 | ->print(); | ||||||
56 | } | ||||||
57 | 1; | ||||||
58 | |||||||
59 | A CGI script that uses CAM::UserApp through that subclass: | ||||||
60 | |||||||
61 | #!perl | ||||||
62 | use Config; | ||||||
63 | use MyApp; | ||||||
64 | my $app = MyApp->new(config => Config->new()); | ||||||
65 | $app->authenticate() or exit(0); | ||||||
66 | my $user = $app->getUser(); | ||||||
67 | if ($app->getCGI()->param('logout')) { | ||||||
68 | $app->deauthenticate(); | ||||||
69 | exit(0); | ||||||
70 | } elsif ($app->getCGI()->param('changepass')) { | ||||||
71 | $app->changePassword($user->getUsername()) or exit(0); | ||||||
72 | } | ||||||
73 | |||||||
74 | print $app->header(); | ||||||
75 | print "Welcome " . $user->getName() . "!\n"; | ||||||
76 | ... | ||||||
77 | |||||||
78 | Note that the class for $user is not defined here. You must build | ||||||
79 | that yourself. The new() and getName() and getUsername() methods | ||||||
80 | shown above are for example only. | ||||||
81 | |||||||
82 | Note that authentication is performed separately from initialization | ||||||
83 | for the sake of applications where login is optional. If your | ||||||
84 | application requires login, we recommend that your CAM::UserApp | ||||||
85 | subclass include methods like the following in addition to those shown | ||||||
86 | in the subclass above. | ||||||
87 | |||||||
88 | use Config; | ||||||
89 | sub new { | ||||||
90 | my $pkg = shift; | ||||||
91 | return $pkg->SUPER::new(config => Config->new(), | ||||||
92 | needPassword => 1, @_); | ||||||
93 | } | ||||||
94 | sub init { | ||||||
95 | my $self = shift; | ||||||
96 | $self->SUPER::init() or return undef; | ||||||
97 | $self->authenticate() or exit(0); | ||||||
98 | if ($app->getCGI()->param('logout')) { | ||||||
99 | $app->deauthenticate(); | ||||||
100 | exit(0); | ||||||
101 | } elsif ($app->getCGI()->param('changepass')) { | ||||||
102 | $app->changePassword($app->getUser()->getUsername()) or exit(0); | ||||||
103 | } | ||||||
104 | return $self; | ||||||
105 | } | ||||||
106 | |||||||
107 | Thus your CGI could look as simple as: | ||||||
108 | |||||||
109 | #!perl | ||||||
110 | use MyApp; | ||||||
111 | my $app = MyApp->new(); | ||||||
112 | print $app->header(); | ||||||
113 | print "Welcome " . $app->getUser()->getName() . "!\n"; | ||||||
114 | ... | ||||||
115 | |||||||
116 | while still including full login support. | ||||||
117 | |||||||
118 | =cut | ||||||
119 | |||||||
120 | #--------------------------------# | ||||||
121 | |||||||
122 | require 5.005_62; | ||||||
123 | 1 | 1 | 28521 | use strict; | |||
1 | 3 | ||||||
1 | 47 | ||||||
124 | 1 | 1 | 5 | use warnings; | |||
1 | 2 | ||||||
1 | 35 | ||||||
125 | 1 | 1 | 453 | use CAM::App; | |||
0 | |||||||
0 | |||||||
126 | |||||||
127 | our @ISA = qw(CAM::App); | ||||||
128 | our $VERSION = '1.01'; | ||||||
129 | |||||||
130 | #--------------------------------# | ||||||
131 | |||||||
132 | =head1 CLASS METHODS | ||||||
133 | |||||||
134 | =over 4 | ||||||
135 | |||||||
136 | =cut | ||||||
137 | |||||||
138 | #--------------------------------# | ||||||
139 | |||||||
140 | =item usernameCGIKey | ||||||
141 | |||||||
142 | =item passwordCGIKey | ||||||
143 | |||||||
144 | =item password1CGIKey | ||||||
145 | |||||||
146 | =item password2CGIKey | ||||||
147 | |||||||
148 | Simple accessors that return the CGI parameter names used to input | ||||||
149 | login details. These are provided so they can be overrided by | ||||||
150 | subclasses. The defaults are: | ||||||
151 | |||||||
152 | usernameCGIKey "username" | ||||||
153 | passwordCGIKey "password" | ||||||
154 | password1CGIKey "password1" | ||||||
155 | password2CGIKey "password2" | ||||||
156 | |||||||
157 | username and password are used for input to authenticate() while | ||||||
158 | password1, password2 and (optionally) password are used for | ||||||
159 | changePassword(). | ||||||
160 | |||||||
161 | =cut | ||||||
162 | |||||||
163 | sub usernameCGIKey { "username" } | ||||||
164 | sub passwordCGIKey { "password" } | ||||||
165 | sub password1CGIKey { "password1" } | ||||||
166 | sub password2CGIKey { "password2" } | ||||||
167 | |||||||
168 | #--------------------------------# | ||||||
169 | |||||||
170 | =item new [argument list...] | ||||||
171 | |||||||
172 | Overrides the superclass constructor to add boolean settings. These | ||||||
173 | settings are used in the authenticate() and changePassword() methods | ||||||
174 | below. Both of those methods allow callers to override this value | ||||||
175 | directly if desired. | ||||||
176 | |||||||
177 | All other arguments are passed on the to the superclass constructor. | ||||||
178 | |||||||
179 | interactive => boolean (default: true) | ||||||
180 | |||||||
181 | If true, login or change password failures yield calls to offerLogin() | ||||||
182 | or offerChangePassword(), respectively. If false, these calls are | ||||||
183 | skipped. The equivalent effect to interactive = false can be achieved | ||||||
184 | by using a no-op offerLogin() or offerChangePassword(), which are in | ||||||
185 | fact the default behaviors for those functions. | ||||||
186 | |||||||
187 | useCGI => boolean (default: true) | ||||||
188 | |||||||
189 | Specifies whether the CGI parameters should be consulted for username | ||||||
190 | and password values, if any. CGI values override session values. | ||||||
191 | |||||||
192 | useSession => boolean (default: true) | ||||||
193 | |||||||
194 | Specifies whether the session record should be consulted for username | ||||||
195 | and password values, if any. | ||||||
196 | |||||||
197 | needPassword => boolean (default: false) | ||||||
198 | |||||||
199 | Specifies whether the user has to enter their old password before a | ||||||
200 | new one can be set in changePassword(). While it defaults to the lax | ||||||
201 | 'false' state, I recommend you set this to true for interactive | ||||||
202 | applications! | ||||||
203 | |||||||
204 | =cut | ||||||
205 | |||||||
206 | sub new | ||||||
207 | { | ||||||
208 | my $pkg = shift; | ||||||
209 | my %params = (@_); | ||||||
210 | |||||||
211 | my $self = $pkg->SUPER::new(%params); | ||||||
212 | $self->{useCGI} = exists $params{useCGI} ? $params{useCGI} : 1; | ||||||
213 | $self->{useSession} = exists $params{useSession} ? $params{useSession} : 1; | ||||||
214 | $self->{needPassword} = exists $params{needPassword} ? $params{needPassword} : 0; | ||||||
215 | $self->{interactive} = exists $params{interactive} ? $params{interactive} : 1; | ||||||
216 | return $self; | ||||||
217 | } | ||||||
218 | #--------------------------------# | ||||||
219 | |||||||
220 | =back | ||||||
221 | |||||||
222 | =head1 INSTANCE METHODS | ||||||
223 | |||||||
224 | =over 4 | ||||||
225 | |||||||
226 | =cut | ||||||
227 | |||||||
228 | #--------------------------------# | ||||||
229 | |||||||
230 | =item retrieveUser USERNAME, PASSWORD | ||||||
231 | |||||||
232 | This method MUST be overridden by a subclass, or authenticate() will | ||||||
233 | never succeed. It should return an object for the specified username | ||||||
234 | and password, or undef if there is no such user. The object can be of | ||||||
235 | any class as long as: 1) it is blessed, 2) it has a | ||||||
236 | recordPassword($password) method that can be called from our | ||||||
237 | changePassword() function. Note that this method MAY be called | ||||||
238 | multiple times during a session, so don't do hit counting in here. | ||||||
239 | |||||||
240 | =cut | ||||||
241 | |||||||
242 | sub retrieveUser | ||||||
243 | { | ||||||
244 | my $self = shift; | ||||||
245 | my $username = shift; | ||||||
246 | my $password = shift; | ||||||
247 | |||||||
248 | my $user; | ||||||
249 | |||||||
250 | # Do something here: | ||||||
251 | # Get a user object (likely a database record) | ||||||
252 | # Make a record of the login? | ||||||
253 | # Tweak the user object? | ||||||
254 | # Return undef if retrieval fails | ||||||
255 | |||||||
256 | # The returned object should have a recordPassword() method | ||||||
257 | |||||||
258 | return $user; | ||||||
259 | } | ||||||
260 | #--------------------------------# | ||||||
261 | |||||||
262 | =item authenticate | ||||||
263 | |||||||
264 | Validate a login. Returns a boolean indicating success. Most | ||||||
265 | applications should abort upon receiving a false response. If the | ||||||
266 | login fails, or if username/password parameters are missing, the | ||||||
267 | offerLogin() method is called before false is returned. For this | ||||||
268 | method to succeed, the retrieveUser() method MUST be implemented by a | ||||||
269 | subclass. After success, the getUser() method will return the cached | ||||||
270 | result from retrieveUser(). | ||||||
271 | |||||||
272 | Optional arguments: | ||||||
273 | |||||||
274 | username => string (default: undef) | ||||||
275 | password => string (default: undef) | ||||||
276 | |||||||
277 | Values to use for login. Overrides CGI and session values. | ||||||
278 | |||||||
279 | useCGI => boolean | ||||||
280 | useSession => boolean | ||||||
281 | interactive => boolean | ||||||
282 | |||||||
283 | These values, if not passed as arguments, are inherited from the | ||||||
284 | CAM::UserApp instance. | ||||||
285 | |||||||
286 | =cut | ||||||
287 | |||||||
288 | sub authenticate | ||||||
289 | { | ||||||
290 | my $self = shift; | ||||||
291 | my %args = (@_); | ||||||
292 | |||||||
293 | my $session; | ||||||
294 | my $cgi; | ||||||
295 | my $passthru = ""; | ||||||
296 | |||||||
297 | foreach my $key ("useCGI", "useSession", "interactive") | ||||||
298 | { | ||||||
299 | $args{$key} = $self->{$key} unless (exists $args{$key}); | ||||||
300 | } | ||||||
301 | |||||||
302 | if ($args{useCGI}) | ||||||
303 | { | ||||||
304 | $cgi = $self->getCGI(); | ||||||
305 | $args{username} ||= $cgi->param($self->usernameCGIKey()); | ||||||
306 | $args{password} ||= $cgi->param($self->passwordCGIKey()); | ||||||
307 | if ($args{interactive}) | ||||||
308 | { | ||||||
309 | foreach my $key ($cgi->param) | ||||||
310 | { | ||||||
311 | next if ($key eq $self->usernameCGIKey() || | ||||||
312 | $key eq $self->passwordCGIKey()); | ||||||
313 | my $hkey = $cgi->escapeHTML($key); | ||||||
314 | foreach my $value ($cgi->param($key)) | ||||||
315 | { | ||||||
316 | $value = "" if (!defined $value); | ||||||
317 | my $hvalue = $cgi->escapeHTML($value); | ||||||
318 | $passthru .= qq[]; | ||||||
319 | } | ||||||
320 | } | ||||||
321 | } | ||||||
322 | } | ||||||
323 | if ($args{useSession}) | ||||||
324 | { | ||||||
325 | $session = $self->getSession(); | ||||||
326 | unless ($session->isNewSession()) | ||||||
327 | { | ||||||
328 | $args{username} ||= $session->get("username"); | ||||||
329 | $args{password} ||= $session->get("password"); | ||||||
330 | } | ||||||
331 | } | ||||||
332 | |||||||
333 | unless ($args{username} || $args{password}) | ||||||
334 | { | ||||||
335 | if ($args{interactive}) | ||||||
336 | { | ||||||
337 | $self->offerLogin(passthru => $passthru); | ||||||
338 | } | ||||||
339 | return undef; | ||||||
340 | } | ||||||
341 | |||||||
342 | unless ($args{username}) | ||||||
343 | { | ||||||
344 | if ($args{interactive}) | ||||||
345 | { | ||||||
346 | $self->offerLogin(error => "Please enter your username", | ||||||
347 | passthru => $passthru); | ||||||
348 | } | ||||||
349 | return undef; | ||||||
350 | } | ||||||
351 | |||||||
352 | unless ($args{password}) | ||||||
353 | { | ||||||
354 | if ($args{interactive}) | ||||||
355 | { | ||||||
356 | $self->offerLogin(error => "Please enter your password", | ||||||
357 | passthru => $passthru); | ||||||
358 | } | ||||||
359 | return undef; | ||||||
360 | } | ||||||
361 | |||||||
362 | my $user = $self->retrieveUser($args{username}, $args{password}); | ||||||
363 | unless ($user) | ||||||
364 | { | ||||||
365 | if ($args{interactive}) | ||||||
366 | { | ||||||
367 | $self->offerLogin(error => "Login failed", | ||||||
368 | passthru => $passthru); | ||||||
369 | } | ||||||
370 | return undef; | ||||||
371 | } | ||||||
372 | |||||||
373 | $self->{User} = $user; | ||||||
374 | |||||||
375 | if ($session) | ||||||
376 | { | ||||||
377 | $session->set(username => $args{username}, | ||||||
378 | password => $args{password}); | ||||||
379 | } | ||||||
380 | |||||||
381 | return $self; | ||||||
382 | } | ||||||
383 | |||||||
384 | #--------------------------------# | ||||||
385 | |||||||
386 | =item getUser | ||||||
387 | |||||||
388 | Returns the User object obtained from authenticate(). If | ||||||
389 | authentication fails, or is never attempted, this method will return | ||||||
390 | undef. | ||||||
391 | |||||||
392 | =cut | ||||||
393 | |||||||
394 | sub getUser | ||||||
395 | { | ||||||
396 | my $self = shift; | ||||||
397 | return $self->{User}; | ||||||
398 | } | ||||||
399 | #--------------------------------# | ||||||
400 | |||||||
401 | =item deauthenticate | ||||||
402 | |||||||
403 | Logs out an authenticated user. If a session is present, it is wiped. | ||||||
404 | After this, the getUser() will return undef. This method returns | ||||||
405 | self. | ||||||
406 | |||||||
407 | Optional arguments: | ||||||
408 | |||||||
409 | useSession => boolean (default: true) | ||||||
410 | |||||||
411 | Specifies whether the session record should be cleared. | ||||||
412 | |||||||
413 | interactive => boolean (default: true) | ||||||
414 | |||||||
415 | If true, the offerLogin() method is called at the end of | ||||||
416 | deauthentication. | ||||||
417 | |||||||
418 | =cut | ||||||
419 | |||||||
420 | sub deauthenticate | ||||||
421 | { | ||||||
422 | my $self = shift; | ||||||
423 | my %args = (@_); | ||||||
424 | |||||||
425 | $args{useSession} = 1 unless (exists $args{useSession}); | ||||||
426 | $args{interactive} = 1 unless (exists $args{interactive}); | ||||||
427 | |||||||
428 | if ($args{useSession}) | ||||||
429 | { | ||||||
430 | my $session = $self->getSession(); | ||||||
431 | if ($session) | ||||||
432 | { | ||||||
433 | $session->clear(); | ||||||
434 | } | ||||||
435 | } | ||||||
436 | delete $self->{User}; | ||||||
437 | if ($args{interactive}) | ||||||
438 | { | ||||||
439 | $self->offerLogin(); | ||||||
440 | } | ||||||
441 | return $self; | ||||||
442 | } | ||||||
443 | |||||||
444 | #--------------------------------# | ||||||
445 | |||||||
446 | =item changePassword | ||||||
447 | |||||||
448 | Change the users password. The user must already be authenticated. | ||||||
449 | If the new password is missing or invalid or if the retyped value does | ||||||
450 | not match, this calls offerChangePassword and returns undef. If the | ||||||
451 | needPassword flag is set, the old password must be entered. It will | ||||||
452 | be validated via the retrieveUser() method. | ||||||
453 | |||||||
454 | Optional arguments: | ||||||
455 | |||||||
456 | username => string (default: undef) | ||||||
457 | password => string (default: undef) | ||||||
458 | |||||||
459 | Values to use for authentication if needPassword is true. Overrides | ||||||
460 | CGI values. | ||||||
461 | |||||||
462 | password1 => string (default: undef) | ||||||
463 | password2 => string (default: undef) | ||||||
464 | |||||||
465 | Values to use for the new password and password verification. | ||||||
466 | Overrides CGI values. | ||||||
467 | |||||||
468 | interactive => boolean | ||||||
469 | useCGI => boolean | ||||||
470 | useSession => boolean | ||||||
471 | needPassword => boolean | ||||||
472 | |||||||
473 | These values, if not passed as arguments, are inherited from the | ||||||
474 | CAM::UserApp instance. | ||||||
475 | |||||||
476 | =cut | ||||||
477 | |||||||
478 | sub changePassword | ||||||
479 | { | ||||||
480 | my $self = shift; | ||||||
481 | my %args = (@_); | ||||||
482 | |||||||
483 | foreach my $key ("useCGI", "useSession", "interactive", "needPassword") | ||||||
484 | { | ||||||
485 | $args{$key} = $self->{$key} unless (exists $args{$key}); | ||||||
486 | } | ||||||
487 | |||||||
488 | my $user = $self->getUser(); | ||||||
489 | my $cgi; | ||||||
490 | |||||||
491 | if ($args{useCGI}) | ||||||
492 | { | ||||||
493 | $cgi = $self->getCGI(); | ||||||
494 | $args{password} ||= $cgi->param($self->passwordCGIKey()); | ||||||
495 | $args{password1} ||= $cgi->param($self->password1CGIKey()); | ||||||
496 | $args{password2} ||= $cgi->param($self->password2CGIKey()); | ||||||
497 | } | ||||||
498 | |||||||
499 | unless ($args{password1} || $args{password2}) | ||||||
500 | { | ||||||
501 | $self->offerChangePassword(); | ||||||
502 | return undef; | ||||||
503 | } | ||||||
504 | |||||||
505 | unless ($args{password1} && $args{password2}) | ||||||
506 | { | ||||||
507 | $self->offerChangePassword(error => "Please fill in all password fields"); | ||||||
508 | return undef; | ||||||
509 | } | ||||||
510 | |||||||
511 | if ($args{needPassword}) | ||||||
512 | { | ||||||
513 | unless ($args{password}) | ||||||
514 | { | ||||||
515 | $self->offerChangePassword(error => "Please fill in all password fields"); | ||||||
516 | return undef; | ||||||
517 | } | ||||||
518 | unless ($args{username}) | ||||||
519 | { | ||||||
520 | $self->offerChangePassword(error => "Error: no username found"); | ||||||
521 | return undef; | ||||||
522 | } | ||||||
523 | unless ($self->retrieveUser($args{username}, $args{password})) | ||||||
524 | { | ||||||
525 | $self->offerChangePassword(error => "Incorrect password"); | ||||||
526 | return undef; | ||||||
527 | } | ||||||
528 | } | ||||||
529 | |||||||
530 | if ($args{password1} ne $args{password2}) | ||||||
531 | { | ||||||
532 | $self->offerChangePassword(error => "The passwords you have entered do not match"); | ||||||
533 | return undef; | ||||||
534 | } | ||||||
535 | |||||||
536 | my $password = $args{password1}; # shorthand | ||||||
537 | unless ($self->validateNewPassword($password)) | ||||||
538 | { | ||||||
539 | $self->offerChangePassword(error => "Invalid password, please try again"); | ||||||
540 | return undef; | ||||||
541 | } | ||||||
542 | |||||||
543 | unless ($user->can("recordPassword") && $user->recordPassword($password)) | ||||||
544 | { | ||||||
545 | $self->offerChangePassword(error => "Unable to record your new password"); | ||||||
546 | return undef; | ||||||
547 | } | ||||||
548 | |||||||
549 | if ($args{useSession}) | ||||||
550 | { | ||||||
551 | # Note! We DO NOT want to create a new session here, so we don't | ||||||
552 | # use the getSession() method. If there is no session, well, so | ||||||
553 | # be it. | ||||||
554 | |||||||
555 | my $session = $self->{session}; | ||||||
556 | if ($session) | ||||||
557 | { | ||||||
558 | $session->set(password => $password); | ||||||
559 | } | ||||||
560 | } | ||||||
561 | |||||||
562 | return $self; | ||||||
563 | } | ||||||
564 | #--------------------------------# | ||||||
565 | |||||||
566 | =item offerLogin | ||||||
567 | |||||||
568 | Display an interactive login. By default, this method is a no-op. | ||||||
569 | Interactive subclasses should override this method. The return value | ||||||
570 | of this method is not used. A sample implementation is presented in | ||||||
571 | the Synopsis above. | ||||||
572 | |||||||
573 | Optional arguments: | ||||||
574 | |||||||
575 | error => string | ||||||
576 | |||||||
577 | Indicates a reason why this method has been called, like "Login | ||||||
578 | failure". On a fresh login, this argument is absent. | ||||||
579 | |||||||
580 | passthru => string | ||||||
581 | |||||||
582 | An accumulation of CGI parameters passed to this program, in the form | ||||||
583 | of '' for each parameter. | ||||||
584 | Implementations are welcome to ignore this, but they should pass it | ||||||
585 | via an HTML form if they want to make the login be 'transparent', | ||||||
586 | i.e., if the program should go back to whatever it was doing before | ||||||
587 | when login is successful login. | ||||||
588 | |||||||
589 | Here's an example HTML template file for use with the offerLogin() | ||||||
590 | implementation in the Synopsis above, using these parameters: | ||||||
591 | |||||||
592 | |
||||||
593 | |||||||
594 | ??error?? ::error:: ??error?? |
||||||
595 | Username: |
||||||
596 | Password: |
||||||
597 | |||||||
598 | ::passthru:: | ||||||
599 | |||||||
600 | |||||||
601 | =cut | ||||||
602 | |||||||
603 | sub offerLogin | ||||||
604 | { | ||||||
605 | my $self = shift; | ||||||
606 | my %args = (@_); | ||||||
607 | |||||||
608 | # do nothing unless subclass overrides | ||||||
609 | } | ||||||
610 | #--------------------------------# | ||||||
611 | |||||||
612 | =item offerChangePassword | ||||||
613 | |||||||
614 | Display an interactive password change screen. By default, this | ||||||
615 | method is a no-op, so interactive subclasses should override this | ||||||
616 | method. The return value of this method is not used. A sample | ||||||
617 | implementation is presented in the Synopsis above. | ||||||
618 | |||||||
619 | Optional arguments: | ||||||
620 | |||||||
621 | error => string | ||||||
622 | |||||||
623 | Indicates a reason why this method has been called, like "Passwords do | ||||||
624 | not match". On first hit, this argument is absent. | ||||||
625 | |||||||
626 | Here's an example HTML template file for use with the | ||||||
627 | offerChangePassword() implementation in the Synopsis above, using | ||||||
628 | this parameters: | ||||||
629 | |||||||
630 | |
||||||
631 | |||||||
632 | ??error?? ::error:: ??error?? |
||||||
633 | Old Password: |
||||||
634 | New Password: |
||||||
635 | Retype Password: |
||||||
636 | |||||||
637 | |||||||
638 | |||||||
639 | =cut | ||||||
640 | |||||||
641 | sub offerChangePassword | ||||||
642 | { | ||||||
643 | my $self = shift; | ||||||
644 | my %args = (@_); | ||||||
645 | |||||||
646 | # do nothing unless subclass overrides | ||||||
647 | } | ||||||
648 | #--------------------------------# | ||||||
649 | |||||||
650 | =item validateNewPassword PASSWORD | ||||||
651 | |||||||
652 | Performs simple checks on the validity of a new password. This | ||||||
653 | implementation only checks that the password is defined and not the | ||||||
654 | null string. Subclasses may implement more rigorous checks. | ||||||
655 | |||||||
656 | =cut | ||||||
657 | |||||||
658 | sub validateNewPassword | ||||||
659 | { | ||||||
660 | my $self = shift; | ||||||
661 | my $password = shift; | ||||||
662 | |||||||
663 | return undef unless (defined $password && $password ne ""); | ||||||
664 | |||||||
665 | return $self; | ||||||
666 | } | ||||||
667 | #--------------------------------# | ||||||
668 | |||||||
669 | 1; | ||||||
670 | __END__ |