[system] / branches / rel-2-1-a1 / webwork-modperl / lib / WeBWorK / Authen.pm Repository:
ViewVC logotype

Annotation of /branches/rel-2-1-a1/webwork-modperl/lib/WeBWorK/Authen.pm

Parent Directory Parent Directory | Revision Log Revision Log


Revision 2527 - (view) (download) (as text)

1 : sh002i 455 ################################################################################
2 : sh002i 1663 # WeBWorK Online Homework Delivery System
3 :     # Copyright © 2000-2003 The WeBWorK Project, http://openwebwork.sf.net/
4 : glarose 2527 # $CVSHeader: webwork2/lib/WeBWorK/Authen.pm,v 1.30 2004/03/15 20:17:35 sh002i Exp $
5 : sh002i 1663 #
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 : sh002i 455 ################################################################################
16 :    
17 : malsyned 305 package WeBWorK::Authen;
18 :    
19 : sh002i 455 =head1 NAME
20 :    
21 :     WeBWorK::Authen - Check user identity, manage session keys.
22 :    
23 :     =cut
24 :    
25 : malsyned 441 use strict;
26 :     use warnings;
27 : sh002i 1683 use Apache::Cookie;
28 : sh002i 1777 use Date::Format;
29 : malsyned 335
30 : sh002i 1777 use constant COOKIE_LIFESPAN => 60*60*24*30; # 30 days
31 :    
32 : sh002i 1885 sub new {
33 :     my ($invocant, $r) = @_;
34 : malsyned 323 my $class = ref($invocant) || $invocant;
35 : sh002i 1885 my $self = {
36 :     r => $r,
37 :     };
38 : malsyned 305 bless $self, $class;
39 :     return $self;
40 :     }
41 :    
42 : sh002i 817 sub checkPassword($$$) {
43 :     my ($self, $userID, $possibleClearPassword) = @_;
44 : sh002i 1885 my $db = $self->{r}->db;
45 :    
46 :     my $Password = $db->getPassword($userID); # checked
47 : sh002i 1636 return 0 unless defined $Password;
48 : sh002i 1885
49 : sh002i 817 my $possibleCryptPassword = crypt($possibleClearPassword, $Password->password());
50 :     return $possibleCryptPassword eq $Password->password();
51 :     }
52 :    
53 :     sub generateKey($$) {
54 :     my ($self, $userID) = @_;
55 : sh002i 1885 my $ce = $self->{r}->ce;
56 :    
57 :     my @chars = @{ $ce->{sessionKeyChars} };
58 :     my $length = $ce->{sessionKeyLength};
59 :    
60 : malsyned 323 srand;
61 : sh002i 817 my $key = join ("", @chars[map rand(@chars), 1 .. $length]);
62 :     return WeBWorK::DB::Record::Key->new(user_id=>$userID, key=>$key, timestamp=>time);
63 :     }
64 :    
65 :     sub checkKey($$$) {
66 :     my ($self, $userID, $possibleKey) = @_;
67 : sh002i 1885 my $ce = $self->{r}->ce;
68 :     my $db = $self->{r}->db;
69 :    
70 :     my $Key = $db->getKey($userID); # checked
71 : sh002i 1636 return 0 unless defined $Key;
72 : sh002i 1885 if (time <= $Key->timestamp()+$ce->{sessionKeyTimeout}) {
73 : sh002i 817 if ($possibleKey eq $Key->key()) {
74 :     # unexpired and matches -- update timestamp
75 :     $Key->timestamp(time);
76 : sh002i 1885 $db->putKey($Key);
77 : sh002i 817 return 1;
78 :     } else {
79 :     # unexpired but doesn't match -- leave timestamp alone
80 :     # we do this to keep an attacker from keeping someone's session
81 :     # alive. (yeah, we don't match IPs.)
82 :     return 0;
83 :     }
84 :     } else {
85 :     # expired -- delete key
86 : sh002i 1885 $db->deleteKey($userID);
87 : sh002i 817 return 0;
88 : malsyned 323 }
89 :     }
90 :    
91 : sh002i 817 sub unexpiredKeyExists($$) {
92 :     my ($self, $userID) = @_;
93 : sh002i 1885 my $ce = $self->{r}->ce;
94 :     my $db = $self->{r}->db;
95 :    
96 :     my $Key = $db->getKey($userID); # checked
97 : sh002i 1636 return 0 unless defined $Key;
98 : sh002i 1885 if (time <= $Key->timestamp()+$ce->{sessionKeyTimeout}) {
99 : sh002i 817 # unexpired, but leave timestamp alone
100 :     return 1;
101 :     } else {
102 :     # expired -- delete key
103 : sh002i 1885 $db->deleteKey($userID);
104 : sh002i 817 return 0;
105 :     }
106 :     }
107 :    
108 : sh002i 1777 sub fetchCookie {
109 : sh002i 1683 my ($self, $user, $key) = @_;
110 :     my $r = $self->{r};
111 : sh002i 1885 my $ce = $r->ce;
112 :     my $urlpath = $r->urlpath;
113 : sh002i 1777
114 : sh002i 1885 my $courseID = $urlpath->arg("courseID");
115 :    
116 : sh002i 1683 my %cookies = Apache::Cookie->fetch;
117 : sh002i 1777 my $cookie = $cookies{"WeBWorKCourseAuthen.$courseID"};
118 :    
119 : sh002i 1683 if ($cookie) {
120 : sh002i 1777 #warn __PACKAGE__, ": fetchCookie: found a cookie for this course: \"", $cookie->as_string, "\"\n";
121 :     #warn __PACKAGE__, ": fetchCookie: cookie has this value: \"", $cookie->value, "\"\n";
122 :     my ($userID, $key) = split "\t", $cookie->value;
123 :     if (defined $userID and defined $key and $userID ne "" and $key ne "") {
124 :     #warn __PACKAGE__, ": fetchCookie: looks good, returning userID=$userID key=$key\n";
125 :     return $userID, $key;
126 :     } else {
127 :     #warn __PACKAGE__, ": fetchCookie: malformed cookie. returning empty strings.\n";
128 :     return "", "";
129 :     }
130 :     } else {
131 :     #warn __PACKAGE__, ": fetchCookie: found no cookie for this course. returning empty strings.\n";
132 :     return "", "";
133 : sh002i 1683 }
134 :     }
135 :    
136 :     sub sendCookie {
137 : sh002i 1777 my ($self, $userID, $key) = @_;
138 : sh002i 1683 my $r = $self->{r};
139 : sh002i 1885 my $ce = $r->ce;
140 : sh002i 1777
141 : sh002i 1885 my $courseID = $r->urlpath->arg("courseID");
142 :    
143 : sh002i 1777 my $expires = time2str("%a, %d-%h-%Y %H:%M:%S %Z", time+COOKIE_LIFESPAN, "GMT");
144 : sh002i 1683 my $cookie = Apache::Cookie->new($r,
145 : sh002i 1777 -name => "WeBWorKCourseAuthen.$courseID",
146 :     -value => "$userID\t$key",
147 :     -expires => $expires,
148 :     -domain => $r->hostname,
149 :     -path => $ce->{webworkURLRoot},
150 :     -secure => 0,
151 : sh002i 1683 );
152 : sh002i 1777 my $cookieString = $cookie->as_string;
153 :    
154 :     #warn __PACKAGE__, ": sendCookie: about to add Set-Cookie header with this string: \"", $cookie->as_string, "\"\n";
155 : sh002i 1683 $r->headers_out->set("Set-Cookie" => $cookie->as_string);
156 :     }
157 :    
158 : sh002i 1685 sub killCookie {
159 :     my ($self) = @_;
160 :     my $r = $self->{r};
161 : sh002i 1885 my $ce = $r->ce;
162 : sh002i 1777
163 : sh002i 1885 my $courseID = $r->urlpath->arg("courseID");
164 :    
165 : sh002i 1777 my $expires = time2str("%a, %d-%h-%Y %H:%M:%S %Z", time-60*60*24, "GMT");
166 : sh002i 1685 my $cookie = Apache::Cookie->new($r,
167 : sh002i 1777 -name => "WeBWorKCourseAuthen.$courseID",
168 :     -value => "\t",
169 :     -expires => $expires,
170 : sh002i 1685 -domain => $r->hostname,
171 :     -path => $ce->{webworkURLRoot},
172 :     -secure => 0,
173 :     );
174 : sh002i 1777 my $cookieString = $cookie->as_string;
175 :    
176 :     #warn __PACKAGE__, ": killCookie: about to add Set-Cookie header with this string: \"", $cookie->as_string, "\"\n";
177 : sh002i 1685 $r->headers_out->set("Set-Cookie" => $cookie->as_string);
178 :     }
179 :    
180 : sh002i 1682 # verify will return 1 if the person is who they say the are. If the
181 :     # verification failed because of of invalid authentication data, a note will be
182 :     # written in the request explaining why it failed. If the request failed because
183 :     # no authentication data was provided, however, no note will be written, as this
184 :     # is expected to happen whenever someone types in a URL manually, and is not
185 :     # considered an error condition.
186 : malsyned 305 sub verify($) {
187 :     my $self = shift;
188 :     my $r = $self->{r};
189 : sh002i 1885 my $ce = $r->ce;
190 :     my $db = $r->db;
191 : malsyned 305
192 : sh002i 817 my $practiceUserPrefix = $ce->{practiceUserPrefix};
193 :     my $debugPracticeUser = $ce->{debugPracticeUser};
194 :    
195 : malsyned 825 my $force_passwd_authen = $r->param('force_passwd_authen');
196 : sh002i 1684 my $login_practice_user = $r->param('login_practice_user');
197 :     my $send_cookie = $r->param("send_cookie");
198 : sh002i 1685
199 : malsyned 441 my $error;
200 : sh002i 817 my $failWithoutError = 0;
201 : sh002i 1685 my $credentialSource = "params";
202 : malsyned 313
203 : sh002i 1685 my $user = $r->param('user');
204 :     my $passwd = $r->param('passwd');
205 :     my $key = $r->param('key');
206 :    
207 : sh002i 1777 my ($cookieUser, $cookieKey) = $self->fetchCookie;
208 :     #warn __PACKAGE__, ": verify: cookieUser=$cookieUser cookieKey=$cookieKey\n";
209 : sh002i 1685
210 : sh002i 817 VERIFY: {
211 :     # This block is here so we can "last" out of it when we've
212 :     # decided whether we're going to succeed or fail.
213 :    
214 : sh002i 1684 if ($login_practice_user) {
215 :     # ignore everything else, find an unused practice user
216 :     my $found = 0;
217 :     foreach my $userID (sort grep m/^$practiceUserPrefix/, $db->listUsers) {
218 :     if (not $self->unexpiredKeyExists($userID)) {
219 :     my $Key = $self->generateKey($userID);
220 :     $db->addKey($Key);
221 :     $r->param("user", $userID);
222 :     $r->param("key", $Key->key);
223 :     $found = 1;
224 :     last;
225 :     }
226 :     }
227 :     unless ($found) {
228 :     $error = "No practice users are available. Please try again in a few minutes.";
229 :     }
230 :     last VERIFY;
231 :     }
232 :    
233 : sh002i 817 # no authentication data was given. this is OK.
234 :     unless (defined $user or defined $passwd or defined $key) {
235 : sh002i 1683 # check to see if a cookie was sent by the browser. if so, use the
236 :     # user and key from the cookie for authentication. note that the
237 :     # cookie is only used if no credentials are sent as parameters.
238 :     if ($cookieUser and $cookieKey) {
239 :     $user = $cookieUser;
240 :     $key = $cookieKey;
241 : sh002i 1684 $r->param("user", $user);
242 :     $r->param("key", $key);
243 : sh002i 1685 $credentialSource = "cookie";
244 : sh002i 1683 } else {
245 :     $failWithoutError = 1;
246 :     last VERIFY;
247 :     }
248 : sh002i 817 }
249 :    
250 : malsyned 825 if (defined $user and $force_passwd_authen) {
251 :     $failWithoutError = 1;
252 :     last VERIFY;
253 :     }
254 :    
255 :     # no user was supplied. somebody's building their own GET
256 : sh002i 817 unless ($user) {
257 :     $error = "You must specify a username.";
258 :     last VERIFY;
259 :     }
260 : gage 1724 ########################################################
261 :     # Make sure user is in the database
262 :     ########################################################
263 :    
264 :     my $userRecord = $db->getUser($user);
265 :     unless (defined $userRecord) { # checked
266 :     $error = "There is no account for $user in this course.";
267 :     last VERIFY;
268 :     }
269 :     ########################################################
270 :     # Make sure the user's status is defined.
271 :     ########################################################
272 :     unless (defined $userRecord->status) {
273 :     $userRecord-> status('C');
274 : sh002i 1777 #warn "Setting status for user $user to C. It was previously undefined.";
275 : gage 1724 }
276 :     unless ($userRecord->status eq 'C') {
277 :     $error = "The user $user has been dropped from this course. ";
278 :     last VERIFY;
279 :    
280 :     }
281 :     ########################################################
282 : sh002i 817 # it's a practice user.
283 : gage 1724 ########################################################
284 : malsyned 349 if ($practiceUserPrefix and $user =~ /^$practiceUserPrefix/) {
285 : sh002i 817 # we're not interested in a practice user's password
286 :     $r->param("passwd", "");
287 : gage 1724
288 :    
289 : sh002i 817 # we've got a key.
290 :     if ($key) {
291 :     if ($self->checkKey($user, $key)) {
292 :     # they key was valid.
293 :     last VERIFY;
294 :     } else {
295 :     # the key was invalid.
296 : malsyned 827 $error = "Your session has timed out due to inactivity. You must login again.";
297 : sh002i 817 last VERIFY;
298 :     }
299 :     }
300 :    
301 :     # -- here we know that a key was not supplied. --
302 :    
303 :     # it's the debug user.
304 :     if ($debugPracticeUser and $user eq $debugPracticeUser) {
305 :     # clobber any existing session, valid or not.
306 :     my $Key = $self->generateKey($user);
307 : sh002i 912 eval { $db->deleteKey($user) };
308 : sh002i 817 $db->addKey($Key);
309 :     $r->param("key", $Key->key());
310 :     last VERIFY;
311 :     }
312 :    
313 :     # an unexpired key exists -- the account is in use.
314 :     if ($self->unexpiredKeyExists($user)) {
315 :     $error = "That practice account is in use.";
316 :     last VERIFY;
317 :     }
318 :    
319 :     # here we know the account is not in use, so we
320 :     # generate a new session key (unexpiredKeyExists
321 :     # deleted any expired key) and succeed!
322 :     my $Key = $self->generateKey($user);
323 :     $db->addKey($Key);
324 :     $r->param("key", $Key->key());
325 :     last VERIFY;
326 :     }
327 :    
328 :     # -- here we know it's a regular user. --
329 : gage 1724
330 :     #########################################################
331 :     # Fail with error message if status is D or dropped
332 :     #########################################################
333 :     if ($db->getUser($user)->status eq 'D' or $db->getUser($user)->status eq 'DROPPED') {
334 :     $error = "The user $user has been dropped from this course. Please contact
335 :     your instructor if this is an error.";
336 :     last VERIFY;
337 : sh002i 817
338 : gage 1724 }
339 : sh002i 817 # a key was supplied.
340 :     if ($key) {
341 :     # we're not interested in a user's password if they're
342 :     # supplying a key
343 :     $r->param("passwd", "");
344 :    
345 :     if ($self->checkKey($user, $key)) {
346 :     # valid key, so succeed.
347 :     last VERIFY;
348 : malsyned 349 } else {
349 : sh002i 817 # invalid key. the login page doesn't propogate the key,
350 :     # so we know this is an expired session.
351 : malsyned 827 $error = "Your session has timed out due to inactivity. You must login again.";
352 : sh002i 817 last VERIFY;
353 : malsyned 349 }
354 :     }
355 : gage 1724
356 : gage 1721 #########################################################
357 : sh002i 817 # a password was supplied.
358 : gage 1721 #########################################################
359 : sh002i 817 if ($passwd) {
360 : gage 1721
361 : sh002i 817 if ($self->checkPassword($user, $passwd)) {
362 :     # valid password, so create a new session. (we don't want
363 :     # to reuse an old one, duh.)
364 :     my $Key = $self->generateKey($user);
365 : sh002i 912 eval { $db->deleteKey($user) };
366 : sh002i 817 $db->addKey($Key);
367 :     $r->param("key", $Key->key());
368 :     # also delete the password
369 :     $r->param("passwd", "");
370 :     last VERIFY;
371 :     } else {
372 :     # incorrect password. fail.
373 :     $error = "Incorrect username or password.";
374 :     last VERIFY;
375 :     }
376 : malsyned 313 }
377 : sh002i 817
378 :     # neither a key or a password were supplied.
379 :     $error = "You must enter a password."
380 :     }
381 :    
382 :     if (defined $error) {
383 : sh002i 1685 # authentication failed, store the error message
384 : sh002i 817 $r->notes("authen_error",$error);
385 : sh002i 1685
386 :     # if we got a cookie, it probably has incorrect information in it. so
387 :     # we want to get rid of it
388 :     if ($cookieUser or $cookieKey) {
389 :     #warn "fail with error: killing cookie";
390 :     $self->killCookie;
391 :     }
392 :    
393 : sh002i 817 return 0;
394 : sh002i 1682 } elsif ($failWithoutError) {
395 : sh002i 1685 # authentication failed, but we don't have any error message to report
396 :    
397 :     # if we got a cookie, it probably has incorrect information in it. so
398 :     # we want to get rid of it
399 :     if ($cookieUser or $cookieKey) {
400 :     #warn "fail without error: killing cookie";
401 :     $self->killCookie;
402 :     }
403 :    
404 : sh002i 1682 return 0;
405 : malsyned 313 } else {
406 : sh002i 1682 # autentication succeeded!
407 : sh002i 1685
408 :     # we send a cookie if any of these conditions are met:
409 :     # (a) a cookie was used for authentication
410 :     # (b) a cookie was sent but not used for authentication, and the
411 :     # credentials used for authentication were the same as those in
412 :     # the cookie
413 :     # (c) the user asked to have a cookie sent and is not a guest user.
414 :     my $usedCookie = ($credentialSource eq "cookie") || 0;
415 : gage 1694
416 :     my $unusedCookieMatched = (defined($key) and defined($cookieUser) and defined($cookieKey) and
417 :     $user eq $cookieUser and $key eq $cookieKey) || 0;
418 : sh002i 1685 my $userRequestsCookie = ($send_cookie and not $login_practice_user) || 0;
419 :     #warn "usedCookie=$usedCookie\n";
420 :     #warn "unusedCookieMatched=$unusedCookieMatched\n";
421 :     #warn "userRequestsCookie=$userRequestsCookie\n";
422 :     if ($usedCookie or $unusedCookieMatched or $userRequestsCookie) {
423 :     #warn "succeed: sending cookie";
424 : sh002i 1683 $self->sendCookie($r->param("user"), $r->param("key"));
425 : sh002i 1685 } elsif ($cookieUser or $cookieKey) {
426 :     # otherwise, we don't want any bad cookies sticking around
427 :     #warn "succeed: killing cookie";
428 :     $self->killCookie;
429 : sh002i 1683 }
430 : sh002i 1682 return 1;
431 : malsyned 305 }
432 : malsyned 313
433 :     # Whatever you do, don't delete this!
434 :     critical($r);
435 : sh002i 1683 # One time, I deleted it, and my mother broke her back, my cat died, and
436 :     # the Pope got a tummy ache. When I replaced the line, I received eternal
437 :     # salvation and a check for USD 500.
438 : malsyned 305 }
439 :    
440 : glarose 2527 sub verifyProctor ($) {
441 :     my $self = shift();
442 :     my $r = $self->{r};
443 :     my $ce = $r->ce;
444 :     my $db = $r->db;
445 :    
446 :     my $user = $r->param('user');
447 :     my $proctorUser = $r->param('proctor_user');
448 :     my $proctorPasswd = $r->param('proctor_passwd');
449 :     my $proctorKey = $r->param('proctor_key');
450 :    
451 :     my $failWithoutError = 0;
452 :     my $error = '';
453 :    
454 :     VERIFY: {
455 :     unless( ( defined($proctorUser) && $proctorUser ) or
456 :     ( defined($proctorPasswd) && $proctorPasswd ) or
457 :     ( defined($proctorKey) && $proctorKey ) ) {
458 :     $failWithoutError = 1;
459 :     last VERIFY;
460 :     }
461 :    
462 :     unless( defined($proctorUser) ) {
463 :     $error = 'Proctor username must be specified.';
464 :     last VERIFY;
465 :     }
466 :    
467 :     my $proctorUserRecord = $db->getUser( $proctorUser );
468 :     unless( defined( $proctorUserRecord ) ) {
469 :     $error = "There is no proctor account for $proctorUser in this course";
470 :     last VERIFY;
471 :     }
472 :    
473 :     unless( ! defined( $proctorUserRecord->status() ) ||
474 :     $proctorUserRecord->status() eq 'C' ) {
475 :     $error = "Proctor user $proctorUser does not have a valid status " .
476 :     "in this course.";
477 :     last VERIFY;
478 :     }
479 :    
480 :     if ( $proctorKey ) {
481 :     $r->param( 'proctor_password', '' );
482 :    
483 :     if ( $self->checkKey("$user,$proctorUser", $proctorKey) ) {
484 :     last VERIFY;
485 :     } else {
486 :     $error = "Invalid or expired proctor session key.";
487 :     last VERIFY;
488 :     }
489 :     }
490 :    
491 :     if ( $proctorPasswd ) {
492 :    
493 :     if ( $self->checkPassword( $proctorUser, $proctorPasswd ) ) {
494 :     my $newKeyObject = $self->generateKey( "$user,$proctorUser" );
495 :     $r->param('proctor_passwd', '');
496 :    
497 :     eval{ $db->deleteKey( "$user,$proctorUser" ); };
498 :     $db->addKey($newKeyObject);
499 :    
500 :     $r->param('proctor_key', $newKeyObject->key());
501 :    
502 :     last VERIFY;
503 :     } else {
504 :     $error = 'Incorrect proctor username or password.';
505 :     last VERIFY;
506 :     }
507 :     }
508 :     }
509 :    
510 :     if ( defined($error) && $error ) {
511 :     $r->notes("authen_error", $error);
512 :     return 0;
513 :    
514 :     } elsif ( $failWithoutError ) {
515 :     return 0;
516 :    
517 :     } else {
518 :     return 1;
519 :     }
520 :     critical($r); # where does critical() come from?
521 :     }
522 :    
523 : malsyned 305 1;
524 : malsyned 522
525 :     __END__
526 :    
527 :     =head1 AUTHOR
528 :    
529 : sh002i 1682 Written by Dennis Lambe Jr., malsyned (at) math.rochester.edu, and Sam
530 :     Hathaway, sh002i (at) math.rochester.edu.
531 : malsyned 522
532 :     =cut

aubreyja at gmail dot com
ViewVC Help
Powered by ViewVC 1.0.9