Parent Directory
|
Revision Log
We now send a cookie if any of these conditions are met: (a) a cookie was used for authentication (b) a cookie was sent but not used for authentication, and the credentials used for authentication were the same as those in the cookie (c) the user asked to have a cookie sent and is not a guest user. Now whenever we're not sending a "real" cookie, we send a cookie with no user or key data that expires "one day ago".
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.23 2003/12/24 00:59:25 sh002i 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 # it's a practice user. 218 if ($practiceUserPrefix and $user =~ /^$practiceUserPrefix/) { 219 # we're not interested in a practice user's password 220 $r->param("passwd", ""); 221 222 # it's a practice user that doesn't exist. 223 unless (defined $db->getUser($user)) { # checked 224 $error = "That practice account does not exist."; 225 last VERIFY; 226 } 227 228 # we've got a key. 229 if ($key) { 230 if ($self->checkKey($user, $key)) { 231 # they key was valid. 232 last VERIFY; 233 } else { 234 # the key was invalid. 235 $error = "Your session has timed out due to inactivity. You must login again."; 236 last VERIFY; 237 } 238 } 239 240 # -- here we know that a key was not supplied. -- 241 242 # it's the debug user. 243 if ($debugPracticeUser and $user eq $debugPracticeUser) { 244 # clobber any existing session, valid or not. 245 my $Key = $self->generateKey($user); 246 eval { $db->deleteKey($user) }; 247 $db->addKey($Key); 248 $r->param("key", $Key->key()); 249 last VERIFY; 250 } 251 252 # an unexpired key exists -- the account is in use. 253 if ($self->unexpiredKeyExists($user)) { 254 $error = "That practice account is in use."; 255 last VERIFY; 256 } 257 258 # here we know the account is not in use, so we 259 # generate a new session key (unexpiredKeyExists 260 # deleted any expired key) and succeed! 261 my $Key = $self->generateKey($user); 262 $db->addKey($Key); 263 $r->param("key", $Key->key()); 264 last VERIFY; 265 } 266 267 # -- here we know it's a regular user. -- 268 269 # a key was supplied. 270 if ($key) { 271 # we're not interested in a user's password if they're 272 # supplying a key 273 $r->param("passwd", ""); 274 275 if ($self->checkKey($user, $key)) { 276 # valid key, so succeed. 277 last VERIFY; 278 } else { 279 # invalid key. the login page doesn't propogate the key, 280 # so we know this is an expired session. 281 $error = "Your session has timed out due to inactivity. You must login again."; 282 last VERIFY; 283 } 284 } 285 286 # a password was supplied. 287 if ($passwd) { 288 if ($self->checkPassword($user, $passwd)) { 289 # valid password, so create a new session. (we don't want 290 # to reuse an old one, duh.) 291 my $Key = $self->generateKey($user); 292 eval { $db->deleteKey($user) }; 293 $db->addKey($Key); 294 $r->param("key", $Key->key()); 295 # also delete the password 296 $r->param("passwd", ""); 297 last VERIFY; 298 } else { 299 # incorrect password. fail. 300 $error = "Incorrect username or password."; 301 last VERIFY; 302 } 303 } 304 305 # neither a key or a password were supplied. 306 $error = "You must enter a password." 307 } 308 309 if (defined $error) { 310 # authentication failed, store the error message 311 $r->notes("authen_error",$error); 312 313 # if we got a cookie, it probably has incorrect information in it. so 314 # we want to get rid of it 315 if ($cookieUser or $cookieKey) { 316 #warn "fail with error: killing cookie"; 317 $self->killCookie; 318 } 319 320 return 0; 321 } elsif ($failWithoutError) { 322 # authentication failed, but we don't have any error message to report 323 324 # if we got a cookie, it probably has incorrect information in it. so 325 # we want to get rid of it 326 if ($cookieUser or $cookieKey) { 327 #warn "fail without error: killing cookie"; 328 $self->killCookie; 329 } 330 331 return 0; 332 } else { 333 # autentication succeeded! 334 335 # we send a cookie if any of these conditions are met: 336 # (a) a cookie was used for authentication 337 # (b) a cookie was sent but not used for authentication, and the 338 # credentials used for authentication were the same as those in 339 # the cookie 340 # (c) the user asked to have a cookie sent and is not a guest user. 341 my $usedCookie = ($credentialSource eq "cookie") || 0; 342 my $unusedCookieMatched = ($user eq $cookieUser and $key eq $cookieKey) || 0; 343 my $userRequestsCookie = ($send_cookie and not $login_practice_user) || 0; 344 #warn "usedCookie=$usedCookie\n"; 345 #warn "unusedCookieMatched=$unusedCookieMatched\n"; 346 #warn "userRequestsCookie=$userRequestsCookie\n"; 347 if ($usedCookie or $unusedCookieMatched or $userRequestsCookie) { 348 #warn "succeed: sending cookie"; 349 $self->sendCookie($r->param("user"), $r->param("key")); 350 } elsif ($cookieUser or $cookieKey) { 351 # otherwise, we don't want any bad cookies sticking around 352 #warn "succeed: killing cookie"; 353 $self->killCookie; 354 } 355 return 1; 356 } 357 358 # Whatever you do, don't delete this! 359 critical($r); 360 # One time, I deleted it, and my mother broke her back, my cat died, and 361 # the Pope got a tummy ache. When I replaced the line, I received eternal 362 # salvation and a check for USD 500. 363 } 364 365 1; 366 367 __END__ 368 369 =head1 AUTHOR 370 371 Written by Dennis Lambe Jr., malsyned (at) math.rochester.edu, and Sam 372 Hathaway, sh002i (at) math.rochester.edu. 373 374 =cut
| aubreyja at gmail dot com | ViewVC Help |
| Powered by ViewVC 1.0.9 |