[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

Revision 313 Revision 1694
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.24 2003/12/25 04:17:18 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
1package WeBWorK::Authen; 17package WeBWorK::Authen;
2 18
19=head1 NAME
20
21WeBWorK::Authen - Check user identity, manage session keys.
22
23=cut
24
25use strict;
26use warnings;
27use Apache::Cookie;
28use Apache::Util qw(unescape_uri_info);
29use Data::Dumper;
30
3sub new($$$) { 31sub new($$$) {
4 my $proto = shift; 32 my $invocant = shift;
5 my $class = ref($proto) || $proto; 33 my $class = ref($invocant) || $invocant;
6 my $self = {}; 34 my $self = {};
7 ($self->{r}, $self->{courseEnvironment}) = @_; 35 ($self->{r}, $self->{ce}, $self->{db}) = @_;
8 bless $self, $class; 36 bless $self, $class;
9 return $self; 37 return $self;
10} 38}
11 39
40sub 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
48sub 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
57sub 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
80sub 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
94sub 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
105sub 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
120sub 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
12# verify will return 1 if the person is who they say the are. 135# verify will return 1 if the person is who they say the are. If the
13# If the verification failed because of of invalid authentication data, 136# verification failed because of of invalid authentication data, a note will be
14# a note will be written in the request explaining why it failed. 137# written in the request explaining why it failed. If the request failed because
15# If the request failed because no authentication data was provided, however, 138# no authentication data was provided, however, no note will be written, as this
16# no note will be written, as this is expected to happen whenever someone 139# is expected to happen whenever someone types in a URL manually, and is not
17# types in a URL manually, and is not considered an error condition. 140# considered an error condition.
18sub verify($) { 141sub verify($) {
19 # Definition: "magic data": passwd or key
20 my $self = shift; 142 my $self = shift;
21 my $r = $self->{r}; 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";
22 157
23 my $user = $r->param('user'); 158 my $user = $r->param('user');
24 my $passwd = $r->param('passwd'); 159 my $passwd = $r->param('passwd');
25 my $key = $r->param('key'); 160 my $key = $r->param('key');
26 161
27 # Get this out of the way first thing. We don't want anything else 162 my ($cookieUser, $cookieKey) = $self->checkCookie;
28 # having access to this. It's bad enough that it goes over the wire 163 $cookieUser ||= "";
29 # plaintext. 164 $cookieKey ||= "";
30 $r->param('passwd',undef);
31 165
32 my $return, $error; 166 VERIFY: {
33 167 # This block is here so we can "last" out of it when we've
34 # The first part of this big conditional checks to make that we have 168 # decided whether we're going to succeed or fail.
35 # all of the form info that we need. It's pretty boring. The kooky 169
36 # authen stuff comes after that. 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.
37 if (!defined $user && !defined $passwd && !defined $key) { 190 unless (defined $user or defined $passwd or defined $key) {
38 # The user hasn't even had a chance to say who he is, so we 191 # check to see if a cookie was sent by the browser. if so, use the
39 # can't hold it against him that we don't know. 192 # user and key from the cookie for authentication. note that the
40 undef $error; 193 # cookie is only used if no credentials are sent as parameters.
41 $return = 0; 194 if ($cookieUser and $cookieKey) {
42 } elsif (!$user) { 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) {
43 $error = "You must specify a username"; 213 $error = "You must specify a username.";
44 $return = 0; 214 last VERIFY;
45 } elsif (!$passwd && !$key) { 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.
46 $error = "You must enter a password"; 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
47 $return = 0; 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;
48 } 329 }
49 # OK, we're done with the trivia. Now lets authenticate. 330
50 # This is the part that will get rewritten after Sam finishes
51 # his work on the database stuff.
52 elsif ($user ne "dennis") {
53 $error = "Unknown user";
54 $return = 0; 331 return 0;
55 } elsif ($passwd) {
56 if ($passwd eq "helloworld") {
57 $r->param('key','tH1siS@pH0n3Yk3y');
58 $return = 1;
59 } else {
60 $error = "Incorrect password";
61 $return = 0;
62 }
63 } elsif ($key) {
64 if ($key eq 'tH1siS@pH0n3Yk3y') {
65 $return = 1;
66 } else {
67 $error = "Your session has expired. You must re-login";
68 $return = 0;
69 }
70 } else { 332 } else {
71 $error = "Unexpected authentication error!"; 333 # autentication succeeded!
72 $return = 0; 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
343 my $unusedCookieMatched = (defined($key) and defined($cookieUser) and defined($cookieKey) and
344 $user eq $cookieUser and $key eq $cookieKey) || 0;
345 my $userRequestsCookie = ($send_cookie and not $login_practice_user) || 0;
346 #warn "usedCookie=$usedCookie\n";
347 #warn "unusedCookieMatched=$unusedCookieMatched\n";
348 #warn "userRequestsCookie=$userRequestsCookie\n";
349 if ($usedCookie or $unusedCookieMatched or $userRequestsCookie) {
350 #warn "succeed: sending cookie";
351 $self->sendCookie($r->param("user"), $r->param("key"));
352 } elsif ($cookieUser or $cookieKey) {
353 # otherwise, we don't want any bad cookies sticking around
354 #warn "succeed: killing cookie";
355 $self->killCookie;
73 } 356 }
74 357 return 1;
75 358 }
76 $r->notes("authen_error",$error);
77 return $return;
78 359
79 # Whatever you do, don't delete this! 360 # Whatever you do, don't delete this!
80 critical($r); 361 critical($r);
362 # One time, I deleted it, and my mother broke her back, my cat died, and
363 # the Pope got a tummy ache. When I replaced the line, I received eternal
364 # salvation and a check for USD 500.
81} 365}
82 366
831; 3671;
368
369__END__
370
371=head1 AUTHOR
372
373Written by Dennis Lambe Jr., malsyned (at) math.rochester.edu, and Sam
374Hathaway, sh002i (at) math.rochester.edu.
375
376=cut

Legend:
Removed from v.313  
changed lines
  Added in v.1694

aubreyja at gmail dot com
ViewVC Help
Powered by ViewVC 1.0.9