Parent Directory
|
Revision Log
added cookie support for authenticaion. the "WeBWorKAuthentication" cookie is used to retrieve the "user" and "key" parameters. the cookie is sent if a "send_cookie" parameter is present, and persists for 30 days unless canceled by an explicit "logout".
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.20 2003/12/09 01:12:30 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 Data::Dumper; 29 30 sub new($$$) { 31 my $invocant = shift; 32 my $class = ref($invocant) || $invocant; 33 my $self = {}; 34 ($self->{r}, $self->{ce}, $self->{db}) = @_; 35 bless $self, $class; 36 return $self; 37 } 38 39 sub checkPassword($$$) { 40 my ($self, $userID, $possibleClearPassword) = @_; 41 my $Password = $self->{db}->getPassword($userID); # checked 42 return 0 unless defined $Password; 43 my $possibleCryptPassword = crypt($possibleClearPassword, $Password->password()); 44 return $possibleCryptPassword eq $Password->password(); 45 } 46 47 sub generateKey($$) { 48 my ($self, $userID) = @_; 49 my @chars = @{ $self->{ce}->{sessionKeyChars} }; 50 my $length = $self->{ce}->{sessionKeyLength}; 51 srand; 52 my $key = join ("", @chars[map rand(@chars), 1 .. $length]); 53 return WeBWorK::DB::Record::Key->new(user_id=>$userID, key=>$key, timestamp=>time); 54 } 55 56 sub checkKey($$$) { 57 my ($self, $userID, $possibleKey) = @_; 58 my $Key = $self->{db}->getKey($userID); # checked 59 return 0 unless defined $Key; 60 if (time <= $Key->timestamp()+$self->{ce}->{sessionKeyTimeout}) { 61 if ($possibleKey eq $Key->key()) { 62 # unexpired and matches -- update timestamp 63 $Key->timestamp(time); 64 $self->{db}->putKey($Key); 65 return 1; 66 } else { 67 # unexpired but doesn't match -- leave timestamp alone 68 # we do this to keep an attacker from keeping someone's session 69 # alive. (yeah, we don't match IPs.) 70 return 0; 71 } 72 } else { 73 # expired -- delete key 74 $self->{db}->deleteKey($userID); 75 return 0; 76 } 77 } 78 79 sub unexpiredKeyExists($$) { 80 my ($self, $userID) = @_; 81 my $Key = $self->{db}->getKey($userID); # checked 82 return 0 unless defined $Key; 83 if (time <= $Key->timestamp()+$self->{ce}->{sessionKeyTimeout}) { 84 # unexpired, but leave timestamp alone 85 return 1; 86 } else { 87 # expired -- delete key 88 $self->{db}->deleteKey($userID); 89 return 0; 90 } 91 } 92 93 sub checkCookie { 94 my ($self, $user, $key) = @_; 95 my $r = $self->{r}; 96 my %cookies = Apache::Cookie->fetch; 97 my $cookie = $cookies{WeBWorKAuthentication}; 98 if ($cookie) { 99 my ($user, $key) = $cookie->value =~ m/^user=([^&]*)&key=([^&]*)$/; 100 return $user, $key; 101 } 102 } 103 104 sub sendCookie { 105 my ($self, $user, $key) = @_; 106 my $r = $self->{r}; 107 my $ce = $self->{ce}; 108 my $cookie = Apache::Cookie->new($r, 109 -name => "WeBWorKAuthentication", 110 -value => "user=$user&key=$key", 111 -expires => "+30D", 112 -domain => $r->hostname, 113 -path => $ce->{webworkURLRoot}, 114 -secure => 0, 115 ); 116 $r->headers_out->set("Set-Cookie" => $cookie->as_string); 117 } 118 119 # verify will return 1 if the person is who they say the are. If the 120 # verification failed because of of invalid authentication data, a note will be 121 # written in the request explaining why it failed. If the request failed because 122 # no authentication data was provided, however, no note will be written, as this 123 # is expected to happen whenever someone types in a URL manually, and is not 124 # considered an error condition. 125 sub verify($) { 126 my $self = shift; 127 my $r = $self->{r}; 128 my $ce = $self->{ce}; 129 my $db = $self->{db}; 130 131 my $practiceUserPrefix = $ce->{practiceUserPrefix}; 132 my $debugPracticeUser = $ce->{debugPracticeUser}; 133 134 my $user = $r->param('user'); 135 my $passwd = $r->param('passwd'); 136 my $key = $r->param('key'); 137 my $force_passwd_authen = $r->param('force_passwd_authen'); 138 139 my $error; 140 my $failWithoutError = 0; 141 142 VERIFY: { 143 # This block is here so we can "last" out of it when we've 144 # decided whether we're going to succeed or fail. 145 146 # no authentication data was given. this is OK. 147 unless (defined $user or defined $passwd or defined $key) { 148 # check to see if a cookie was sent by the browser. if so, use the 149 # user and key from the cookie for authentication. note that the 150 # cookie is only used if no credentials are sent as parameters. 151 my ($cookieUser, $cookieKey) = $self->checkCookie; 152 if ($cookieUser and $cookieKey) { 153 $r->param("user", $cookieUser); 154 $r->param("key", $cookieKey); 155 $user = $cookieUser; 156 $key = $cookieKey; 157 } else { 158 $failWithoutError = 1; 159 last VERIFY; 160 } 161 } 162 163 if (defined $user and $force_passwd_authen) { 164 $failWithoutError = 1; 165 last VERIFY; 166 } 167 168 # no user was supplied. somebody's building their own GET 169 unless ($user) { 170 $error = "You must specify a username."; 171 last VERIFY; 172 } 173 174 # it's a practice user. 175 if ($practiceUserPrefix and $user =~ /^$practiceUserPrefix/) { 176 # we're not interested in a practice user's password 177 $r->param("passwd", ""); 178 179 # it's a practice user that doesn't exist. 180 unless (defined $db->getUser($user)) { # checked 181 $error = "That practice account does not exist."; 182 last VERIFY; 183 } 184 185 # we've got a key. 186 if ($key) { 187 if ($self->checkKey($user, $key)) { 188 # they key was valid. 189 last VERIFY; 190 } else { 191 # the key was invalid. 192 $error = "Your session has timed out due to inactivity. You must login again."; 193 last VERIFY; 194 } 195 } 196 197 # -- here we know that a key was not supplied. -- 198 199 # it's the debug user. 200 if ($debugPracticeUser and $user eq $debugPracticeUser) { 201 # clobber any existing session, valid or not. 202 my $Key = $self->generateKey($user); 203 eval { $db->deleteKey($user) }; 204 $db->addKey($Key); 205 $r->param("key", $Key->key()); 206 last VERIFY; 207 } 208 209 # an unexpired key exists -- the account is in use. 210 if ($self->unexpiredKeyExists($user)) { 211 $error = "That practice account is in use."; 212 last VERIFY; 213 } 214 215 # here we know the account is not in use, so we 216 # generate a new session key (unexpiredKeyExists 217 # deleted any expired key) and succeed! 218 my $Key = $self->generateKey($user); 219 $db->addKey($Key); 220 $r->param("key", $Key->key()); 221 last VERIFY; 222 } 223 224 # -- here we know it's a regular user. -- 225 226 # a key was supplied. 227 if ($key) { 228 # we're not interested in a user's password if they're 229 # supplying a key 230 $r->param("passwd", ""); 231 232 if ($self->checkKey($user, $key)) { 233 # valid key, so succeed. 234 last VERIFY; 235 } else { 236 # invalid key. the login page doesn't propogate the key, 237 # so we know this is an expired session. 238 $error = "Your session has timed out due to inactivity. You must login again."; 239 last VERIFY; 240 } 241 } 242 243 # a password was supplied. 244 if ($passwd) { 245 if ($self->checkPassword($user, $passwd)) { 246 # valid password, so create a new session. (we don't want 247 # to reuse an old one, duh.) 248 my $Key = $self->generateKey($user); 249 eval { $db->deleteKey($user) }; 250 $db->addKey($Key); 251 $r->param("key", $Key->key()); 252 # also delete the password 253 $r->param("passwd", ""); 254 last VERIFY; 255 } else { 256 # incorrect password. fail. 257 $error = "Incorrect username or password."; 258 last VERIFY; 259 } 260 } 261 262 # neither a key or a password were supplied. 263 $error = "You must enter a password." 264 } 265 266 if (defined $error) { 267 # authentication failed, in a bad way 268 $r->notes("authen_error",$error); 269 return 0; 270 } elsif ($failWithoutError) { 271 # authentication failed, but not in a bad way 272 return 0; 273 } else { 274 # autentication succeeded! 275 # send a cookie with the user and key that were accepted. 276 if ($r->param("send_cookie")) { 277 $self->sendCookie($r->param("user"), $r->param("key")); 278 } 279 return 1; 280 } 281 282 # Whatever you do, don't delete this! 283 critical($r); 284 # One time, I deleted it, and my mother broke her back, my cat died, and 285 # the Pope got a tummy ache. When I replaced the line, I received eternal 286 # salvation and a check for USD 500. 287 } 288 289 1; 290 291 __END__ 292 293 =head1 AUTHOR 294 295 Written by Dennis Lambe Jr., malsyned (at) math.rochester.edu, and Sam 296 Hathaway, sh002i (at) math.rochester.edu. 297 298 =cut
| aubreyja at gmail dot com | ViewVC Help |
| Powered by ViewVC 1.0.9 |