[system] / trunk / webwork2 / lib / WeBWorK / Authen.pm Repository:
ViewVC logotype

Annotation of /trunk/webwork2/lib/WeBWorK/Authen.pm

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1719 - (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 : gage 1719 # $CVSHeader: webwork-modperl/lib/WeBWorK/Authen.pm,v 1.25 2004/01/03 17:12:55 gage 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 1685 use Apache::Util qw(unescape_uri_info);
29 : sh002i 1683 use Data::Dumper;
30 : malsyned 335
31 : malsyned 305 sub new($$$) {
32 : malsyned 323 my $invocant = shift;
33 :     my $class = ref($invocant) || $invocant;
34 : malsyned 305 my $self = {};
35 : sh002i 817 ($self->{r}, $self->{ce}, $self->{db}) = @_;
36 : malsyned 305 bless $self, $class;
37 :     return $self;
38 :     }
39 :    
40 : sh002i 817 sub checkPassword($$$) {
41 :     my ($self, $userID, $possibleClearPassword) = @_;
42 : sh002i 1636 my $Password = $self->{db}->getPassword($userID); # checked
43 :     return 0 unless defined $Password;
44 : sh002i 817 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 : malsyned 323 srand;
53 : sh002i 817 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 : sh002i 1636 my $Key = $self->{db}->getKey($userID); # checked
60 :     return 0 unless defined $Key;
61 : sh002i 817 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 : malsyned 323 }
78 :     }
79 :    
80 : sh002i 817 sub unexpiredKeyExists($$) {
81 :     my ($self, $userID) = @_;
82 : sh002i 1636 my $Key = $self->{db}->getKey($userID); # checked
83 :     return 0 unless defined $Key;
84 : sh002i 817 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 : sh002i 1683 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 : sh002i 1685 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 : sh002i 1682 # 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 : malsyned 305 sub verify($) {
142 :     my $self = shift;
143 :     my $r = $self->{r};
144 : sh002i 817 my $ce = $self->{ce};
145 :     my $db = $self->{db};
146 : malsyned 305
147 : sh002i 817 my $practiceUserPrefix = $ce->{practiceUserPrefix};
148 :     my $debugPracticeUser = $ce->{debugPracticeUser};
149 :    
150 : malsyned 825 my $force_passwd_authen = $r->param('force_passwd_authen');
151 : sh002i 1684 my $login_practice_user = $r->param('login_practice_user');
152 :     my $send_cookie = $r->param("send_cookie");
153 : sh002i 1685
154 : malsyned 441 my $error;
155 : sh002i 817 my $failWithoutError = 0;
156 : sh002i 1685 my $credentialSource = "params";
157 : malsyned 313
158 : sh002i 1685 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 : sh002i 817 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 : sh002i 1684 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 : sh002i 817 # no authentication data was given. this is OK.
190 :     unless (defined $user or defined $passwd or defined $key) {
191 : sh002i 1683 # 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 : sh002i 1684 $r->param("user", $user);
198 :     $r->param("key", $key);
199 : sh002i 1685 $credentialSource = "cookie";
200 : sh002i 1683 } else {
201 :     $failWithoutError = 1;
202 :     last VERIFY;
203 :     }
204 : sh002i 817 }
205 :    
206 : malsyned 825 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 : sh002i 817 unless ($user) {
213 :     $error = "You must specify a username.";
214 :     last VERIFY;
215 :     }
216 :    
217 :     # it's a practice user.
218 : malsyned 349 if ($practiceUserPrefix and $user =~ /^$practiceUserPrefix/) {
219 : sh002i 817 # 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 : sh002i 1636 unless (defined $db->getUser($user)) { # checked
224 : sh002i 817 $error = "That practice account does not exist.";
225 :     last VERIFY;
226 :     }
227 : gage 1719 unless ($db->getUser($user)->status eq 'C') {
228 :     $error = "The user $user has been dropped from this course. ";
229 :     last VERIFY;
230 : sh002i 817
231 : gage 1719 }
232 : sh002i 817 # we've got a key.
233 :     if ($key) {
234 :     if ($self->checkKey($user, $key)) {
235 :     # they key was valid.
236 :     last VERIFY;
237 :     } else {
238 :     # the key was invalid.
239 : malsyned 827 $error = "Your session has timed out due to inactivity. You must login again.";
240 : sh002i 817 last VERIFY;
241 :     }
242 :     }
243 :    
244 :     # -- here we know that a key was not supplied. --
245 :    
246 :     # it's the debug user.
247 :     if ($debugPracticeUser and $user eq $debugPracticeUser) {
248 :     # clobber any existing session, valid or not.
249 :     my $Key = $self->generateKey($user);
250 : sh002i 912 eval { $db->deleteKey($user) };
251 : sh002i 817 $db->addKey($Key);
252 :     $r->param("key", $Key->key());
253 :     last VERIFY;
254 :     }
255 :    
256 :     # an unexpired key exists -- the account is in use.
257 :     if ($self->unexpiredKeyExists($user)) {
258 :     $error = "That practice account is in use.";
259 :     last VERIFY;
260 :     }
261 :    
262 :     # here we know the account is not in use, so we
263 :     # generate a new session key (unexpiredKeyExists
264 :     # deleted any expired key) and succeed!
265 :     my $Key = $self->generateKey($user);
266 :     $db->addKey($Key);
267 :     $r->param("key", $Key->key());
268 :     last VERIFY;
269 :     }
270 :    
271 :     # -- here we know it's a regular user. --
272 :    
273 :     # a key was supplied.
274 :     if ($key) {
275 :     # we're not interested in a user's password if they're
276 :     # supplying a key
277 :     $r->param("passwd", "");
278 :    
279 :     if ($self->checkKey($user, $key)) {
280 :     # valid key, so succeed.
281 :     last VERIFY;
282 : malsyned 349 } else {
283 : sh002i 817 # invalid key. the login page doesn't propogate the key,
284 :     # so we know this is an expired session.
285 : malsyned 827 $error = "Your session has timed out due to inactivity. You must login again.";
286 : sh002i 817 last VERIFY;
287 : malsyned 349 }
288 :     }
289 : sh002i 817
290 :     # a password was supplied.
291 :     if ($passwd) {
292 : gage 1719 # if user has been dropped from the course fail with error message
293 :     unless ($db->getUser($user)->status eq 'C') {
294 :     $error = "The user $user has been dropped from this course. Please contact
295 :     your instructor if this is an error.";
296 :     last VERIFY;
297 :    
298 :     }
299 : sh002i 817 if ($self->checkPassword($user, $passwd)) {
300 :     # valid password, so create a new session. (we don't want
301 :     # to reuse an old one, duh.)
302 :     my $Key = $self->generateKey($user);
303 : sh002i 912 eval { $db->deleteKey($user) };
304 : sh002i 817 $db->addKey($Key);
305 :     $r->param("key", $Key->key());
306 :     # also delete the password
307 :     $r->param("passwd", "");
308 :     last VERIFY;
309 :     } else {
310 :     # incorrect password. fail.
311 :     $error = "Incorrect username or password.";
312 :     last VERIFY;
313 :     }
314 : malsyned 313 }
315 : sh002i 817
316 :     # neither a key or a password were supplied.
317 :     $error = "You must enter a password."
318 :     }
319 :    
320 :     if (defined $error) {
321 : sh002i 1685 # authentication failed, store the error message
322 : sh002i 817 $r->notes("authen_error",$error);
323 : sh002i 1685
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 with error: killing cookie";
328 :     $self->killCookie;
329 :     }
330 :    
331 : sh002i 817 return 0;
332 : sh002i 1682 } elsif ($failWithoutError) {
333 : sh002i 1685 # authentication failed, but we don't have any error message to report
334 :    
335 :     # if we got a cookie, it probably has incorrect information in it. so
336 :     # we want to get rid of it
337 :     if ($cookieUser or $cookieKey) {
338 :     #warn "fail without error: killing cookie";
339 :     $self->killCookie;
340 :     }
341 :    
342 : sh002i 1682 return 0;
343 : malsyned 313 } else {
344 : sh002i 1682 # autentication succeeded!
345 : sh002i 1685
346 :     # we send a cookie if any of these conditions are met:
347 :     # (a) a cookie was used for authentication
348 :     # (b) a cookie was sent but not used for authentication, and the
349 :     # credentials used for authentication were the same as those in
350 :     # the cookie
351 :     # (c) the user asked to have a cookie sent and is not a guest user.
352 :     my $usedCookie = ($credentialSource eq "cookie") || 0;
353 : gage 1694
354 :     my $unusedCookieMatched = (defined($key) and defined($cookieUser) and defined($cookieKey) and
355 :     $user eq $cookieUser and $key eq $cookieKey) || 0;
356 : sh002i 1685 my $userRequestsCookie = ($send_cookie and not $login_practice_user) || 0;
357 :     #warn "usedCookie=$usedCookie\n";
358 :     #warn "unusedCookieMatched=$unusedCookieMatched\n";
359 :     #warn "userRequestsCookie=$userRequestsCookie\n";
360 :     if ($usedCookie or $unusedCookieMatched or $userRequestsCookie) {
361 :     #warn "succeed: sending cookie";
362 : sh002i 1683 $self->sendCookie($r->param("user"), $r->param("key"));
363 : sh002i 1685 } elsif ($cookieUser or $cookieKey) {
364 :     # otherwise, we don't want any bad cookies sticking around
365 :     #warn "succeed: killing cookie";
366 :     $self->killCookie;
367 : sh002i 1683 }
368 : sh002i 1682 return 1;
369 : malsyned 305 }
370 : malsyned 313
371 :     # Whatever you do, don't delete this!
372 :     critical($r);
373 : sh002i 1683 # One time, I deleted it, and my mother broke her back, my cat died, and
374 :     # the Pope got a tummy ache. When I replaced the line, I received eternal
375 :     # salvation and a check for USD 500.
376 : malsyned 305 }
377 :    
378 :     1;
379 : malsyned 522
380 :     __END__
381 :    
382 :     =head1 AUTHOR
383 :    
384 : sh002i 1682 Written by Dennis Lambe Jr., malsyned (at) math.rochester.edu, and Sam
385 :     Hathaway, sh002i (at) math.rochester.edu.
386 : malsyned 522
387 :     =cut

aubreyja at gmail dot com
ViewVC Help
Powered by ViewVC 1.0.9