| 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__ |