[system] / branches / rel-2-4-patches / webwork2 / lib / WeBWorK / Authen.pm Repository:
ViewVC logotype

View of /branches/rel-2-4-patches/webwork2/lib/WeBWorK/Authen.pm

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1694 - (download) (as text) (annotate)
Sat Jan 3 17:12:55 2004 UTC (9 years, 4 months ago) by gage
Original Path: trunk/webwork2/lib/WeBWorK/Authen.pm
File size: 11258 byte(s)
Modified line at 344 to check whether $key is defined.
If $key was not defined then there was a reported error
in defining $unusedCookieMatched.

--Mike

    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 
   17 package WeBWorK::Authen;
   18 
   19 =head1 NAME
   20 
   21 WeBWorK::Authen - Check user identity, manage session keys.
   22 
   23 =cut
   24 
   25 use strict;
   26 use warnings;
   27 use Apache::Cookie;
   28 use Apache::Util qw(unescape_uri_info);
   29 use Data::Dumper;
   30 
   31 sub new($$$) {
   32   my $invocant = shift;
   33   my $class = ref($invocant) || $invocant;
   34   my $self = {};
   35   ($self->{r}, $self->{ce}, $self->{db}) = @_;
   36   bless $self, $class;
   37   return $self;
   38 }
   39 
   40 sub 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 
   48 sub 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 
   57 sub 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 
   80 sub 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 
   94 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 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 # 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 sub verify($) {
  142   my $self = shift;
  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";
  157 
  158   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   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     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.
  190     unless (defined $user or defined $passwd or defined $key) {
  191       # 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         $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) {
  213       $error = "You must specify a username.";
  214       last VERIFY;
  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.
  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 
  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;
  329     }
  330 
  331     return 0;
  332   } else {
  333     # autentication succeeded!
  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;
  356     }
  357     return 1;
  358   }
  359 
  360   # Whatever you do, don't delete this!
  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.
  365 }
  366 
  367 1;
  368 
  369 __END__
  370 
  371 =head1 AUTHOR
  372 
  373 Written by Dennis Lambe Jr., malsyned (at) math.rochester.edu, and Sam
  374 Hathaway, sh002i (at) math.rochester.edu.
  375 
  376 =cut

aubreyja at gmail dot com
ViewVC Help
Powered by ViewVC 1.0.9