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

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

Parent Directory Parent Directory | Revision Log Revision Log | View Patch Patch

trunk/webwork-modperl/lib/WeBWorK/Authen.pm Revision 455 branches/rel-2-1-a1/webwork-modperl/lib/WeBWorK/Authen.pm Revision 2527
1################################################################################ 1################################################################################
2# WeBWorK mod_perl (c) 1995-2002 WeBWorK Team, Univeristy of Rochester 2# WeBWorK Online Homework Delivery System
3# $Id$ 3# Copyright © 2000-2003 The WeBWorK Project, http://openwebwork.sf.net/
4# $CVSHeader: webwork2/lib/WeBWorK/Authen.pm,v 1.30 2004/03/15 20:17:35 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.
4################################################################################ 15################################################################################
5 16
6package WeBWorK::Authen; 17package WeBWorK::Authen;
7 18
8=head1 NAME 19=head1 NAME
11 22
12=cut 23=cut
13 24
14use strict; 25use strict;
15use warnings; 26use warnings;
16use WeBWorK::DB::Auth; 27use Apache::Cookie;
28use Date::Format;
17 29
30use constant COOKIE_LIFESPAN => 60*60*24*30; # 30 days
31
18sub new($$$) { 32sub new {
19 my $invocant = shift; 33 my ($invocant, $r) = @_;
20 my $class = ref($invocant) || $invocant; 34 my $class = ref($invocant) || $invocant;
21 my $self = {}; 35 my $self = {
22 ($self->{r}, $self->{courseEnvironment}) = @_; 36 r => $r,
37 };
23 bless $self, $class; 38 bless $self, $class;
24 return $self; 39 return $self;
25} 40}
26 41
42sub checkPassword($$$) {
43 my ($self, $userID, $possibleClearPassword) = @_;
44 my $db = $self->{r}->db;
45
46 my $Password = $db->getPassword($userID); # checked
47 return 0 unless defined $Password;
48
49 my $possibleCryptPassword = crypt($possibleClearPassword, $Password->password());
50 return $possibleCryptPassword eq $Password->password();
51}
52
27sub generate_key { 53sub generateKey($$) {
28 # Package constants. These should never be changed in other places ever 54 my ($self, $userID) = @_;
29 my $key_length = 40; # number of chars in each key 55 my $ce = $self->{r}->ce;
30 my @key_chars = ('A'..'Z', 'a'..'z', '0'..'9', '.', '^', '/', '!', '*'); 56
31 57 my @chars = @{ $ce->{sessionKeyChars} };
32 my $i = $key_length; 58 my $length = $ce->{sessionKeyLength};
33 my $key = ''; 59
34 srand; 60 srand;
35 while($i) { 61 my $key = join ("", @chars[map rand(@chars), 1 .. $length]);
36 $key .= $key_chars[rand(@key_chars)]; 62 return WeBWorK::DB::Record::Key->new(user_id=>$userID, key=>$key, timestamp=>time);
37 $i--; 63}
64
65sub checkKey($$$) {
66 my ($self, $userID, $possibleKey) = @_;
67 my $ce = $self->{r}->ce;
68 my $db = $self->{r}->db;
69
70 my $Key = $db->getKey($userID); # checked
71 return 0 unless defined $Key;
72 if (time <= $Key->timestamp()+$ce->{sessionKeyTimeout}) {
73 if ($possibleKey eq $Key->key()) {
74 # unexpired and matches -- update timestamp
75 $Key->timestamp(time);
76 $db->putKey($Key);
77 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 $db->deleteKey($userID);
87 return 0;
38 } 88 }
89}
90
91sub unexpiredKeyExists($$) {
92 my ($self, $userID) = @_;
93 my $ce = $self->{r}->ce;
94 my $db = $self->{r}->db;
95
96 my $Key = $db->getKey($userID); # checked
97 return 0 unless defined $Key;
98 if (time <= $Key->timestamp()+$ce->{sessionKeyTimeout}) {
99 # unexpired, but leave timestamp alone
39 return $key; 100 return 1;
101 } else {
102 # expired -- delete key
103 $db->deleteKey($userID);
104 return 0;
105 }
40} 106}
41 107
108sub fetchCookie {
109 my ($self, $user, $key) = @_;
110 my $r = $self->{r};
111 my $ce = $r->ce;
112 my $urlpath = $r->urlpath;
113
114 my $courseID = $urlpath->arg("courseID");
115
116 my %cookies = Apache::Cookie->fetch;
117 my $cookie = $cookies{"WeBWorKCourseAuthen.$courseID"};
118
119 if ($cookie) {
120 #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 }
134}
135
136sub sendCookie {
137 my ($self, $userID, $key) = @_;
138 my $r = $self->{r};
139 my $ce = $r->ce;
140
141 my $courseID = $r->urlpath->arg("courseID");
142
143 my $expires = time2str("%a, %d-%h-%Y %H:%M:%S %Z", time+COOKIE_LIFESPAN, "GMT");
144 my $cookie = Apache::Cookie->new($r,
145 -name => "WeBWorKCourseAuthen.$courseID",
146 -value => "$userID\t$key",
147 -expires => $expires,
148 -domain => $r->hostname,
149 -path => $ce->{webworkURLRoot},
150 -secure => 0,
151 );
152 my $cookieString = $cookie->as_string;
153
154 #warn __PACKAGE__, ": sendCookie: about to add Set-Cookie header with this string: \"", $cookie->as_string, "\"\n";
155 $r->headers_out->set("Set-Cookie" => $cookie->as_string);
156}
157
158sub killCookie {
159 my ($self) = @_;
160 my $r = $self->{r};
161 my $ce = $r->ce;
162
163 my $courseID = $r->urlpath->arg("courseID");
164
165 my $expires = time2str("%a, %d-%h-%Y %H:%M:%S %Z", time-60*60*24, "GMT");
166 my $cookie = Apache::Cookie->new($r,
167 -name => "WeBWorKCourseAuthen.$courseID",
168 -value => "\t",
169 -expires => $expires,
170 -domain => $r->hostname,
171 -path => $ce->{webworkURLRoot},
172 -secure => 0,
173 );
174 my $cookieString = $cookie->as_string;
175
176 #warn __PACKAGE__, ": killCookie: about to add Set-Cookie header with this string: \"", $cookie->as_string, "\"\n";
177 $r->headers_out->set("Set-Cookie" => $cookie->as_string);
178}
179
42# verify will return 1 if the person is who they say the are. 180# verify will return 1 if the person is who they say the are. If the
43# If the verification failed because of of invalid authentication data, 181# verification failed because of of invalid authentication data, a note will be
44# a note will be written in the request explaining why it failed. 182# written in the request explaining why it failed. If the request failed because
45# If the request failed because no authentication data was provided, however, 183# no authentication data was provided, however, no note will be written, as this
46# no note will be written, as this is expected to happen whenever someone 184# is expected to happen whenever someone types in a URL manually, and is not
47# types in a URL manually, and is not considered an error condition. 185# considered an error condition.
48sub verify($) { 186sub verify($) {
49 my $self = shift; 187 my $self = shift;
50 my $r = $self->{r}; 188 my $r = $self->{r};
51 my $course_env = $self->{courseEnvironment}; 189 my $ce = $r->ce;
190 my $db = $r->db;
191
192 my $practiceUserPrefix = $ce->{practiceUserPrefix};
193 my $debugPracticeUser = $ce->{debugPracticeUser};
194
195 my $force_passwd_authen = $r->param('force_passwd_authen');
196 my $login_practice_user = $r->param('login_practice_user');
197 my $send_cookie = $r->param("send_cookie");
198
199 my $error;
200 my $failWithoutError = 0;
201 my $credentialSource = "params";
52 202
53 my $user = $r->param('user'); 203 my $user = $r->param('user');
54 my $passwd = $r->param('passwd'); 204 my $passwd = $r->param('passwd');
55 my $key = $r->param('key'); 205 my $key = $r->param('key');
56 my $time = time;
57 206
58 # I wanted to get rid of that passwd up here for security reasons, 207 my ($cookieUser, $cookieKey) = $self->fetchCookie;
59 # but usability dictates that we not clear out invalid passwords. 208 #warn __PACKAGE__, ": verify: cookieUser=$cookieUser cookieKey=$cookieKey\n";
60 #$r->param('passwd',undef);
61 209
62 my $error; 210 VERIFY: {
63 my $return; 211 # This block is here so we can "last" out of it when we've
64 212 # decided whether we're going to succeed or fail.
65 my $auth = WeBWorK::DB::Auth->new($course_env); 213
66 214 if ($login_practice_user) {
67 # The first part of this big conditional checks to make that we have 215 # ignore everything else, find an unused practice user
68 # all of the form info that we need. It's pretty boring. The kooky 216 my $found = 0;
69 # authen stuff comes after that. 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 # no authentication data was given. this is OK.
70 if (!defined $user && !defined $passwd && !defined $key) { 234 unless (defined $user or defined $passwd or defined $key) {
71 # The user hasn't even had a chance to say who he is, so we 235 # check to see if a cookie was sent by the browser. if so, use the
72 # can't hold it against him that we don't know. 236 # user and key from the cookie for authentication. note that the
73 undef $error; 237 # cookie is only used if no credentials are sent as parameters.
74 $return = 0; 238 if ($cookieUser and $cookieKey) {
75 } elsif (!$user) { 239 $user = $cookieUser;
240 $key = $cookieKey;
241 $r->param("user", $user);
242 $r->param("key", $key);
243 $credentialSource = "cookie";
244 } else {
245 $failWithoutError = 1;
246 last VERIFY;
247 }
248 }
249
250 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 unless ($user) {
76 $error = "You must specify a username"; 257 $error = "You must specify a username.";
77 $return = 0; 258 last VERIFY;
78 } elsif (!$passwd && !$key) { 259 }
260 ########################################################
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 #warn "Setting status for user $user to C. It was previously undefined.";
275 }
276 unless ($userRecord->status eq 'C') {
277 $error = "The user $user has been dropped from this course. ";
278 last VERIFY;
279
280 }
281 ########################################################
282 # it's a practice user.
283 ########################################################
284 if ($practiceUserPrefix and $user =~ /^$practiceUserPrefix/) {
285 # we're not interested in a practice user's password
286 $r->param("passwd", "");
287
288
289 # 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 $error = "Your session has timed out due to inactivity. You must login again.";
297 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 eval { $db->deleteKey($user) };
308 $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
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
338 }
339 # 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 } else {
349 # invalid key. the login page doesn't propogate the key,
350 # so we know this is an expired session.
351 $error = "Your session has timed out due to inactivity. You must login again.";
352 last VERIFY;
353 }
354 }
355
356 #########################################################
357 # a password was supplied.
358 #########################################################
359 if ($passwd) {
360
361 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 eval { $db->deleteKey($user) };
366 $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 }
377
378 # neither a key or a password were supplied.
79 $error = "You must enter a password"; 379 $error = "You must enter a password."
80 $return = 0;
81 } 380 }
82 # OK, we're done with the trivia. Now lets authenticate. 381
83 elsif ($passwd) { 382 if (defined $error) {
84 # A bit of extra logic for practice users 383 # authentication failed, store the error message
85 # Practice users are different because: 384 $r->notes("authen_error",$error);
86 # - They aren't allowed to log in if an active key exists 385
87 # (except for $debugPracticeUser) 386 # if we got a cookie, it probably has incorrect information in it. so
88 # - They are allowed to log in with any password 387 # we want to get rid of it
89 my $practiceUserPrefix = $course_env->{"practiceUserPrefix"}; 388 if ($cookieUser or $cookieKey) {
90 my $debugPracticeUser = $course_env->{"debugPracticeUser"}; 389 #warn "fail with error: killing cookie";
91 if ($practiceUserPrefix and $user =~ /^$practiceUserPrefix/) { 390 $self->killCookie;
92 if (!$auth->getPassword($user)) { # the only way DB::Auth provides for checking the existence of a user
93 $error = "That practice account does not exist";
94 $return = 0;
95 } elsif ($auth->getKey($user) and $user ne $debugPracticeUser) {
96 $error = "That practice account is in use";
97 $return = 0;
98 } else {
99 $key = generate_key;
100 $auth->setKey($user, $key);
101 $r->param('key',$key);
102 $return = 1;
103 } 391 }
104 } 392
105 # Not a practice user. Do normal authentication.
106 elsif ($auth->verifyPassword($user, $passwd)) {
107 # Remove the passwd field from subsequent requests.
108 $r->param('passwd',undef);
109 $key = $auth->getKey($user) || generate_key;
110 $auth->setKey($user, $key);
111 $r->param('key',$key);
112 $return = 1;
113 } else {
114 $error = "Incorrect username or password";
115 $return = 0; 393 return 0;
394 } elsif ($failWithoutError) {
395 # authentication failed, but we don't have any error message to report
116 } 396
117 } elsif ($key) { 397 # if we got a cookie, it probably has incorrect information in it. so
118 # The timestamp gets updated by verifyKey 398 # we want to get rid of it
119 if ($auth->verifyKey($user, $key)) { 399 if ($cookieUser or $cookieKey) {
120 $return = 1; 400 #warn "fail without error: killing cookie";
121 } else { 401 $self->killCookie;
122 $error = "Your session has expired. You must login again"; 402 }
403
123 $return = 0; 404 return 0;
124 }
125 } else { 405 } else {
126 $error = "Unexpected authentication error!"; 406 # autentication succeeded!
407
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
416 my $unusedCookieMatched = (defined($key) and defined($cookieUser) and defined($cookieKey) and
417 $user eq $cookieUser and $key eq $cookieKey) || 0;
418 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 $self->sendCookie($r->param("user"), $r->param("key"));
425 } elsif ($cookieUser or $cookieKey) {
426 # otherwise, we don't want any bad cookies sticking around
427 #warn "succeed: killing cookie";
428 $self->killCookie;
429 }
127 $return = 0; 430 return 1;
128 } 431 }
129
130 $r->notes("authen_error",$error) if defined($error);
131 return $return;
132 432
133 # Whatever you do, don't delete this! 433 # Whatever you do, don't delete this!
134 critical($r); 434 critical($r);
435 # 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}
439
440sub 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?
135} 521}
136 522
1371; 5231;
524
525__END__
526
527=head1 AUTHOR
528
529Written by Dennis Lambe Jr., malsyned (at) math.rochester.edu, and Sam
530Hathaway, sh002i (at) math.rochester.edu.
531
532=cut

Legend:
Removed from v.455  
changed lines
  Added in v.2527

aubreyja at gmail dot com
ViewVC Help
Powered by ViewVC 1.0.9