[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 1684 - (view) (download) (as text)
Original Path: trunk/webwork-modperl/lib/WeBWorK/Authen.pm

1 : sh002i 455 ################################################################################
2 : sh002i 1663 # WeBWorK Online Homework Delivery System
3 :     # Copyright © 2000-2003 The WeBWorK Project, http://openwebwork.sf.net/
4 : sh002i 1684 # $CVSHeader: webwork-modperl/lib/WeBWorK/Authen.pm,v 1.22 2003/12/23 06:03:33 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 :     use Data::Dumper;
29 : malsyned 335
30 : malsyned 305 sub new($$$) {
31 : malsyned 323 my $invocant = shift;
32 :     my $class = ref($invocant) || $invocant;
33 : malsyned 305 my $self = {};
34 : sh002i 817 ($self->{r}, $self->{ce}, $self->{db}) = @_;
35 : malsyned 305 bless $self, $class;
36 :     return $self;
37 :     }
38 :    
39 : sh002i 817 sub checkPassword($$$) {
40 :     my ($self, $userID, $possibleClearPassword) = @_;
41 : sh002i 1636 my $Password = $self->{db}->getPassword($userID); # checked
42 :     return 0 unless defined $Password;
43 : sh002i 817 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 : malsyned 323 srand;
52 : sh002i 817 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 : sh002i 1636 my $Key = $self->{db}->getKey($userID); # checked
59 :     return 0 unless defined $Key;
60 : sh002i 817 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 : malsyned 323 }
77 :     }
78 :    
79 : sh002i 817 sub unexpiredKeyExists($$) {
80 :     my ($self, $userID) = @_;
81 : sh002i 1636 my $Key = $self->{db}->getKey($userID); # checked
82 :     return 0 unless defined $Key;
83 : sh002i 817 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 : sh002i 1683 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 : sh002i 1682 # 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 : malsyned 305 sub verify($) {
126 :     my $self = shift;
127 :     my $r = $self->{r};
128 : sh002i 817 my $ce = $self->{ce};
129 :     my $db = $self->{db};
130 : malsyned 305
131 : sh002i 817 my $practiceUserPrefix = $ce->{practiceUserPrefix};
132 :     my $debugPracticeUser = $ce->{debugPracticeUser};
133 :    
134 : malsyned 313 my $user = $r->param('user');
135 :     my $passwd = $r->param('passwd');
136 :     my $key = $r->param('key');
137 : malsyned 825 my $force_passwd_authen = $r->param('force_passwd_authen');
138 : sh002i 1684 my $login_practice_user = $r->param('login_practice_user');
139 :     my $send_cookie = $r->param("send_cookie");
140 : malsyned 441 my $error;
141 : sh002i 817 my $failWithoutError = 0;
142 : malsyned 313
143 : sh002i 817 VERIFY: {
144 :     # This block is here so we can "last" out of it when we've
145 :     # decided whether we're going to succeed or fail.
146 :    
147 : sh002i 1684 if ($login_practice_user) {
148 :     # ignore everything else, find an unused practice user
149 :     my $found = 0;
150 :     foreach my $userID (sort grep m/^$practiceUserPrefix/, $db->listUsers) {
151 :     if (not $self->unexpiredKeyExists($userID)) {
152 :     my $Key = $self->generateKey($userID);
153 :     $db->addKey($Key);
154 :     $r->param("user", $userID);
155 :     $r->param("key", $Key->key);
156 :     $found = 1;
157 :     last;
158 :     }
159 :     }
160 :     unless ($found) {
161 :     $error = "No practice users are available. Please try again in a few minutes.";
162 :     }
163 :     last VERIFY;
164 :     }
165 :    
166 : sh002i 817 # no authentication data was given. this is OK.
167 :     unless (defined $user or defined $passwd or defined $key) {
168 : sh002i 1683 # check to see if a cookie was sent by the browser. if so, use the
169 :     # user and key from the cookie for authentication. note that the
170 :     # cookie is only used if no credentials are sent as parameters.
171 :     my ($cookieUser, $cookieKey) = $self->checkCookie;
172 :     if ($cookieUser and $cookieKey) {
173 :     $user = $cookieUser;
174 :     $key = $cookieKey;
175 : sh002i 1684 $r->param("user", $user);
176 :     $r->param("key", $key);
177 : sh002i 1683 } else {
178 :     $failWithoutError = 1;
179 :     last VERIFY;
180 :     }
181 : sh002i 817 }
182 :    
183 : malsyned 825 if (defined $user and $force_passwd_authen) {
184 :     $failWithoutError = 1;
185 :     last VERIFY;
186 :     }
187 :    
188 :     # no user was supplied. somebody's building their own GET
189 : sh002i 817 unless ($user) {
190 :     $error = "You must specify a username.";
191 :     last VERIFY;
192 :     }
193 :    
194 :     # it's a practice user.
195 : malsyned 349 if ($practiceUserPrefix and $user =~ /^$practiceUserPrefix/) {
196 : sh002i 817 # we're not interested in a practice user's password
197 :     $r->param("passwd", "");
198 :    
199 :     # it's a practice user that doesn't exist.
200 : sh002i 1636 unless (defined $db->getUser($user)) { # checked
201 : sh002i 817 $error = "That practice account does not exist.";
202 :     last VERIFY;
203 :     }
204 :    
205 :     # we've got a key.
206 :     if ($key) {
207 :     if ($self->checkKey($user, $key)) {
208 :     # they key was valid.
209 :     last VERIFY;
210 :     } else {
211 :     # the key was invalid.
212 : malsyned 827 $error = "Your session has timed out due to inactivity. You must login again.";
213 : sh002i 817 last VERIFY;
214 :     }
215 :     }
216 :    
217 :     # -- here we know that a key was not supplied. --
218 :    
219 :     # it's the debug user.
220 :     if ($debugPracticeUser and $user eq $debugPracticeUser) {
221 :     # clobber any existing session, valid or not.
222 :     my $Key = $self->generateKey($user);
223 : sh002i 912 eval { $db->deleteKey($user) };
224 : sh002i 817 $db->addKey($Key);
225 :     $r->param("key", $Key->key());
226 :     last VERIFY;
227 :     }
228 :    
229 :     # an unexpired key exists -- the account is in use.
230 :     if ($self->unexpiredKeyExists($user)) {
231 :     $error = "That practice account is in use.";
232 :     last VERIFY;
233 :     }
234 :    
235 :     # here we know the account is not in use, so we
236 :     # generate a new session key (unexpiredKeyExists
237 :     # deleted any expired key) and succeed!
238 :     my $Key = $self->generateKey($user);
239 :     $db->addKey($Key);
240 :     $r->param("key", $Key->key());
241 :     last VERIFY;
242 :     }
243 :    
244 :     # -- here we know it's a regular user. --
245 :    
246 :     # a key was supplied.
247 :     if ($key) {
248 :     # we're not interested in a user's password if they're
249 :     # supplying a key
250 :     $r->param("passwd", "");
251 :    
252 :     if ($self->checkKey($user, $key)) {
253 :     # valid key, so succeed.
254 :     last VERIFY;
255 : malsyned 349 } else {
256 : sh002i 817 # invalid key. the login page doesn't propogate the key,
257 :     # so we know this is an expired session.
258 : malsyned 827 $error = "Your session has timed out due to inactivity. You must login again.";
259 : sh002i 817 last VERIFY;
260 : malsyned 349 }
261 :     }
262 : sh002i 817
263 :     # a password was supplied.
264 :     if ($passwd) {
265 :     if ($self->checkPassword($user, $passwd)) {
266 :     # valid password, so create a new session. (we don't want
267 :     # to reuse an old one, duh.)
268 :     my $Key = $self->generateKey($user);
269 : sh002i 912 eval { $db->deleteKey($user) };
270 : sh002i 817 $db->addKey($Key);
271 :     $r->param("key", $Key->key());
272 :     # also delete the password
273 :     $r->param("passwd", "");
274 :     last VERIFY;
275 :     } else {
276 :     # incorrect password. fail.
277 :     $error = "Incorrect username or password.";
278 :     last VERIFY;
279 :     }
280 : malsyned 313 }
281 : sh002i 817
282 :     # neither a key or a password were supplied.
283 :     $error = "You must enter a password."
284 :     }
285 :    
286 :     if (defined $error) {
287 : sh002i 1682 # authentication failed, in a bad way
288 : sh002i 817 $r->notes("authen_error",$error);
289 :     return 0;
290 : sh002i 1682 } elsif ($failWithoutError) {
291 :     # authentication failed, but not in a bad way
292 :     return 0;
293 : malsyned 313 } else {
294 : sh002i 1682 # autentication succeeded!
295 : sh002i 1683 # send a cookie with the user and key that were accepted.
296 : sh002i 1684 if ($send_cookie and not $login_practice_user) {
297 : sh002i 1683 $self->sendCookie($r->param("user"), $r->param("key"));
298 :     }
299 : sh002i 1682 return 1;
300 : malsyned 305 }
301 : malsyned 313
302 :     # Whatever you do, don't delete this!
303 :     critical($r);
304 : sh002i 1683 # One time, I deleted it, and my mother broke her back, my cat died, and
305 :     # the Pope got a tummy ache. When I replaced the line, I received eternal
306 :     # salvation and a check for USD 500.
307 : malsyned 305 }
308 :    
309 :     1;
310 : malsyned 522
311 :     __END__
312 :    
313 :     =head1 AUTHOR
314 :    
315 : sh002i 1682 Written by Dennis Lambe Jr., malsyned (at) math.rochester.edu, and Sam
316 :     Hathaway, sh002i (at) math.rochester.edu.
317 : malsyned 522
318 :     =cut

aubreyja at gmail dot com
ViewVC Help
Powered by ViewVC 1.0.9