Parent Directory
|
Revision Log
Fixed some errors in my first fix for Authen.pm The goal is to make sure that the existance of a users record and of a users status is checked before it is used. Authen.pm could use some cleaning up. Probably best to do this when thinking about how one would plug this into LDAP or to moodle's authenticator.
1 ################################################################################ 2 # WeBWorK Online Homework Delivery System 3 # Copyright © 2000-2003 The WeBWorK Project, http://openwebwork.sf.net/ 4 # $CVSHeader: webwork-modperl/lib/WeBWorK/Authen.pm,v 1.26 2004/01/17 17:05:53 gage Exp $ 5 # 6 # This program is free software; you can redistribute it and/or modify it under 7 # the terms of either: (a) the GNU General Public License as published by the 8 # Free Software Foundation; either version 2, or (at your option) any later 9 # version, or (b) the "Artistic License" which comes with this package. 10 # 11 # This program is distributed in the hope that it will be useful, but WITHOUT 12 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 13 # FOR A PARTICULAR PURPOSE. See either the GNU General Public License or the 14 # Artistic License for more details. 15 ################################################################################ 16 17 package WeBWorK::Authen; 18 19 =head1 NAME 20 21 WeBWorK::Authen - Check user identity, manage session keys. 22 23 =cut 24 25 use strict; 26 use warnings; 27 use Apache::Cookie; 28 use Apache::Util qw(unescape_uri_info); 29 use Data::Dumper; 30 31 sub new($$$) { 32 my $invocant = shift; 33 my $class = ref($invocant) || $invocant; 34 my $self = {}; 35 ($self->{r}, $self->{ce}, $self->{db}) = @_; 36 bless $self, $class; 37 return $self; 38 } 39 40 sub checkPassword($$$) { 41 my ($self, $userID, $possibleClearPassword) = @_; 42 my $Password = $self->{db}->getPassword($userID); # checked 43 return 0 unless defined $Password; 44 my $possibleCryptPassword = crypt($possibleClearPassword, $Password->password()); 45 return $possibleCryptPassword eq $Password->password(); 46 } 47 48 sub generateKey($$) { 49 my ($self, $userID) = @_; 50 my @chars = @{ $self->{ce}->{sessionKeyChars} }; 51 my $length = $self->{ce}->{sessionKeyLength}; 52 srand; 53 my $key = join ("", @chars[map rand(@chars), 1 .. $length]); 54 return WeBWorK::DB::Record::Key->new(user_id=>$userID, key=>$key, timestamp=>time); 55 } 56 57 sub checkKey($$$) { 58 my ($self, $userID, $possibleKey) = @_; 59 my $Key = $self->{db}->getKey($userID); # checked 60 return 0 unless defined $Key; 61 if (time <= $Key->timestamp()+$self->{ce}->{sessionKeyTimeout}) { 62 if ($possibleKey eq $Key->key()) { 63 # unexpired and matches -- update timestamp 64 $Key->timestamp(time); 65 $self->{db}->putKey($Key); 66 return 1; 67 } else { 68 # unexpired but doesn't match -- leave timestamp alone 69 # we do this to keep an attacker from keeping someone's session 70 # alive. (yeah, we don't match IPs.) 71 return 0; 72 } 73 } else { 74 # expired -- delete key 75 $self->{db}->deleteKey($userID); 76 return 0; 77 } 78 } 79 80 sub unexpiredKeyExists($$) { 81 my ($self, $userID) = @_; 82 my $Key = $self->{db}->getKey($userID); # checked 83 return 0 unless defined $Key; 84 if (time <= $Key->timestamp()+$self->{ce}->{sessionKeyTimeout}) { 85 # unexpired, but leave timestamp alone 86 return 1; 87 } else { 88 # expired -- delete key 89 $self->{db}->deleteKey($userID); 90 return 0; 91 } 92 } 93 94 sub checkCookie { 95 my ($self, $user, $key) = @_; 96 my $r = $self->{r}; 97 my %cookies = Apache::Cookie->fetch; 98 my $cookie = $cookies{WeBWorKAuthentication}; 99 if ($cookie) { 100 my ($user, $key) = $cookie->value =~ m/^user=([^&]*)&key=([^&]*)$/; 101 return $user, $key; 102 } 103 } 104 105 sub sendCookie { 106 my ($self, $user, $key) = @_; 107 my $r = $self->{r}; 108 my $ce = $self->{ce}; 109 my $cookie = Apache::Cookie->new($r, 110 -name => "WeBWorKAuthentication", 111 -value => "user=$user&key=$key", 112 -expires => "+30D", 113 -domain => $r->hostname, 114 -path => $ce->{webworkURLRoot}, 115 -secure => 0, 116 ); 117 $r->headers_out->set("Set-Cookie" => $cookie->as_string); 118 } 119 120 sub killCookie { 121 my ($self) = @_; 122 my $r = $self->{r}; 123 my $ce = $self->{ce}; 124 my $cookie = Apache::Cookie->new($r, 125 -name => "WeBWorKAuthentication", 126 -value => "user=&key=", 127 -expires => "-1D", 128 -domain => $r->hostname, 129 -path => $ce->{webworkURLRoot}, 130 -secure => 0, 131 ); 132 $r->headers_out->set("Set-Cookie" => $cookie->as_string); 133 } 134 135 # verify will return 1 if the person is who they say the are. If the 136 # verification failed because of of invalid authentication data, a note will be 137 # written in the request explaining why it failed. If the request failed because 138 # no authentication data was provided, however, no note will be written, as this 139 # is expected to happen whenever someone types in a URL manually, and is not 140 # considered an error condition. 141 sub verify($) { 142 my $self = shift; 143 my $r = $self->{r}; 144 my $ce = $self->{ce}; 145 my $db = $self->{db}; 146 147 my $practiceUserPrefix = $ce->{practiceUserPrefix}; 148 my $debugPracticeUser = $ce->{debugPracticeUser}; 149 150 my $force_passwd_authen = $r->param('force_passwd_authen'); 151 my $login_practice_user = $r->param('login_practice_user'); 152 my $send_cookie = $r->param("send_cookie"); 153 154 my $error; 155 my $failWithoutError = 0; 156 my $credentialSource = "params"; 157 158 my $user = $r->param('user'); 159 my $passwd = $r->param('passwd'); 160 my $key = $r->param('key'); 161 162 my ($cookieUser, $cookieKey) = $self->checkCookie; 163 $cookieUser ||= ""; 164 $cookieKey ||= ""; 165 166 VERIFY: { 167 # This block is here so we can "last" out of it when we've 168 # decided whether we're going to succeed or fail. 169 170 if ($login_practice_user) { 171 # ignore everything else, find an unused practice user 172 my $found = 0; 173 foreach my $userID (sort grep m/^$practiceUserPrefix/, $db->listUsers) { 174 if (not $self->unexpiredKeyExists($userID)) { 175 my $Key = $self->generateKey($userID); 176 $db->addKey($Key); 177 $r->param("user", $userID); 178 $r->param("key", $Key->key); 179 $found = 1; 180 last; 181 } 182 } 183 unless ($found) { 184 $error = "No practice users are available. Please try again in a few minutes."; 185 } 186 last VERIFY; 187 } 188 189 # no authentication data was given. this is OK. 190 unless (defined $user or defined $passwd or defined $key) { 191 # check to see if a cookie was sent by the browser. if so, use the 192 # user and key from the cookie for authentication. note that the 193 # cookie is only used if no credentials are sent as parameters. 194 if ($cookieUser and $cookieKey) { 195 $user = $cookieUser; 196 $key = $cookieKey; 197 $r->param("user", $user); 198 $r->param("key", $key); 199 $credentialSource = "cookie"; 200 } else { 201 $failWithoutError = 1; 202 last VERIFY; 203 } 204 } 205 206 if (defined $user and $force_passwd_authen) { 207 $failWithoutError = 1; 208 last VERIFY; 209 } 210 211 # no user was supplied. somebody's building their own GET 212 unless ($user) { 213 $error = "You must specify a username."; 214 last VERIFY; 215 } 216 ######################################################## 217 # Make sure user is in the database 218 ######################################################## 219 220 my $userRecord = $db->getUser($user); 221 unless (defined $userRecord) { # checked 222 $error = "There is no account for $user in this course."; 223 last VERIFY; 224 } 225 ######################################################## 226 # Make sure the user's status is defined. 227 ######################################################## 228 unless (defined $userRecord->status) { 229 $userRecord-> status('C'); 230 warn "Setting status for user $user to C. It was previously undefined."; 231 } 232 unless ($userRecord->status eq 'C') { 233 $error = "The user $user has been dropped from this course. "; 234 last VERIFY; 235 236 } 237 ######################################################## 238 # it's a practice user. 239 ######################################################## 240 if ($practiceUserPrefix and $user =~ /^$practiceUserPrefix/) { 241 # we're not interested in a practice user's password 242 $r->param("passwd", ""); 243 244 245 # we've got a key. 246 if ($key) { 247 if ($self->checkKey($user, $key)) { 248 # they key was valid. 249 last VERIFY; 250 } else { 251 # the key was invalid. 252 $error = "Your session has timed out due to inactivity. You must login again."; 253 last VERIFY; 254 } 255 } 256 257 # -- here we know that a key was not supplied. -- 258 259 # it's the debug user. 260 if ($debugPracticeUser and $user eq $debugPracticeUser) { 261 # clobber any existing session, valid or not. 262 my $Key = $self->generateKey($user); 263 eval { $db->deleteKey($user) }; 264 $db->addKey($Key); 265 $r->param("key", $Key->key()); 266 last VERIFY; 267 } 268 269 # an unexpired key exists -- the account is in use. 270 if ($self->unexpiredKeyExists($user)) { 271 $error = "That practice account is in use."; 272 last VERIFY; 273 } 274 275 # here we know the account is not in use, so we 276 # generate a new session key (unexpiredKeyExists 277 # deleted any expired key) and succeed! 278 my $Key = $self->generateKey($user); 279 $db->addKey($Key); 280 $r->param("key", $Key->key()); 281 last VERIFY; 282 } 283 284 # -- here we know it's a regular user. -- 285 286 ######################################################### 287 # Fail with error message if status is D or dropped 288 ######################################################### 289 if ($db->getUser($user)->status eq 'D' or $db->getUser($user)->status eq 'DROPPED') { 290 $error = "The user $user has been dropped from this course. Please contact 291 your instructor if this is an error."; 292 last VERIFY; 293 294 } 295 # a key was supplied. 296 if ($key) { 297 # we're not interested in a user's password if they're 298 # supplying a key 299 $r->param("passwd", ""); 300 301 if ($self->checkKey($user, $key)) { 302 # valid key, so succeed. 303 last VERIFY; 304 } else { 305 # invalid key. the login page doesn't propogate the key, 306 # so we know this is an expired session. 307 $error = "Your session has timed out due to inactivity. You must login again."; 308 last VERIFY; 309 } 310 } 311 312 ######################################################### 313 # a password was supplied. 314 ######################################################### 315 if ($passwd) { 316 317 if ($self->checkPassword($user, $passwd)) { 318 # valid password, so create a new session. (we don't want 319 # to reuse an old one, duh.) 320 my $Key = $self->generateKey($user); 321 eval { $db->deleteKey($user) }; 322 $db->addKey($Key); 323 $r->param("key", $Key->key()); 324 # also delete the password 325 $r->param("passwd", ""); 326 last VERIFY; 327 } else { 328 # incorrect password. fail. 329 $error = "Incorrect username or password."; 330 last VERIFY; 331 } 332 } 333 334 # neither a key or a password were supplied. 335 $error = "You must enter a password." 336 } 337 338 if (defined $error) { 339 # authentication failed, store the error message 340 $r->notes("authen_error",$error); 341 342 # if we got a cookie, it probably has incorrect information in it. so 343 # we want to get rid of it 344 if ($cookieUser or $cookieKey) { 345 #warn "fail with error: killing cookie"; 346 $self->killCookie; 347 } 348 349 return 0; 350 } elsif ($failWithoutError) { 351 # authentication failed, but we don't have any error message to report 352 353 # if we got a cookie, it probably has incorrect information in it. so 354 # we want to get rid of it 355 if ($cookieUser or $cookieKey) { 356 #warn "fail without error: killing cookie"; 357 $self->killCookie; 358 } 359 360 return 0; 361 } else { 362 # autentication succeeded! 363 364 # we send a cookie if any of these conditions are met: 365 # (a) a cookie was used for authentication 366 # (b) a cookie was sent but not used for authentication, and the 367 # credentials used for authentication were the same as those in 368 # the cookie 369 # (c) the user asked to have a cookie sent and is not a guest user. 370 my $usedCookie = ($credentialSource eq "cookie") || 0; 371 372 my $unusedCookieMatched = (defined($key) and defined($cookieUser) and defined($cookieKey) and 373 $user eq $cookieUser and $key eq $cookieKey) || 0; 374 my $userRequestsCookie = ($send_cookie and not $login_practice_user) || 0; 375 #warn "usedCookie=$usedCookie\n"; 376 #warn "unusedCookieMatched=$unusedCookieMatched\n"; 377 #warn "userRequestsCookie=$userRequestsCookie\n"; 378 if ($usedCookie or $unusedCookieMatched or $userRequestsCookie) { 379 #warn "succeed: sending cookie"; 380 $self->sendCookie($r->param("user"), $r->param("key")); 381 } elsif ($cookieUser or $cookieKey) { 382 # otherwise, we don't want any bad cookies sticking around 383 #warn "succeed: killing cookie"; 384 $self->killCookie; 385 } 386 return 1; 387 } 388 389 # Whatever you do, don't delete this! 390 critical($r); 391 # One time, I deleted it, and my mother broke her back, my cat died, and 392 # the Pope got a tummy ache. When I replaced the line, I received eternal 393 # salvation and a check for USD 500. 394 } 395 396 1; 397 398 __END__ 399 400 =head1 AUTHOR 401 402 Written by Dennis Lambe Jr., malsyned (at) math.rochester.edu, and Sam 403 Hathaway, sh002i (at) math.rochester.edu. 404 405 =cut
| aubreyja at gmail dot com | ViewVC Help |
| Powered by ViewVC 1.0.9 |