[system] / trunk / webwork-modperl / lib / WeBWorK / Authen.pm Repository:
ViewVC logotype

View of /trunk/webwork-modperl/lib/WeBWorK/Authen.pm

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1724 - (download) (as text) (annotate)
Sun Jan 18 03:39:06 2004 UTC (9 years, 5 months ago) by gage
File size: 12489 byte(s)
Fixed some errors in my first fix for Authen.pm
The goal is to make sure that the existance of a users
record and of a users status is checked before it is used.

Authen.pm could use some cleaning up.  Probably best to do this
when thinking about how one would plug this into LDAP or to
moodle's authenticator.

    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.26 2004/01/17 17:05:53 gage 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     # Make sure user is in the database
  218     ########################################################
  219 
  220     my $userRecord    =   $db->getUser($user);
  221     unless (defined $userRecord) { # checked
  222       $error = "There is no account for $user in this course.";
  223       last VERIFY;
  224     }
  225     ########################################################
  226     # Make sure the user's status is defined.
  227     ########################################################
  228     unless (defined $userRecord->status) {
  229       $userRecord-> status('C');
  230       warn "Setting status for user $user to C.  It was previously undefined.";
  231     }
  232     unless ($userRecord->status eq 'C') {
  233       $error  = "The user $user has been dropped from this course. ";
  234       last VERIFY;
  235 
  236     }
  237     ########################################################
  238     # it's a practice user.
  239     ########################################################
  240     if ($practiceUserPrefix and $user =~ /^$practiceUserPrefix/) {
  241       # we're not interested in a practice user's password
  242       $r->param("passwd", "");
  243 
  244 
  245       # we've got a key.
  246       if ($key) {
  247         if ($self->checkKey($user, $key)) {
  248           # they key was valid.
  249           last VERIFY;
  250         } else {
  251           # the key was invalid.
  252           $error = "Your session has timed out due to inactivity. You must login again.";
  253           last VERIFY;
  254         }
  255       }
  256 
  257       # -- here we know that a key was not supplied. --
  258 
  259       # it's the debug user.
  260       if ($debugPracticeUser and $user eq $debugPracticeUser) {
  261         # clobber any existing session, valid or not.
  262         my $Key = $self->generateKey($user);
  263         eval { $db->deleteKey($user) };
  264         $db->addKey($Key);
  265         $r->param("key", $Key->key());
  266         last VERIFY;
  267       }
  268 
  269       # an unexpired key exists -- the account is in use.
  270       if ($self->unexpiredKeyExists($user)) {
  271         $error = "That practice account is in use.";
  272         last VERIFY;
  273       }
  274 
  275       # here we know the account is not in use, so we
  276       # generate a new  session key (unexpiredKeyExists
  277       # deleted any expired key) and succeed!
  278       my $Key = $self->generateKey($user);
  279       $db->addKey($Key);
  280       $r->param("key", $Key->key());
  281       last VERIFY;
  282     }
  283 
  284     # -- here we know it's a regular user. --
  285 
  286     #########################################################
  287     # Fail with error message if status is D or dropped
  288     #########################################################
  289     if ($db->getUser($user)->status eq 'D' or $db->getUser($user)->status eq 'DROPPED') {
  290       $error  = "The user $user has been dropped from this course. Please contact
  291       your instructor if this is an error.";
  292       last VERIFY;
  293 
  294     }
  295     # a key was supplied.
  296     if ($key) {
  297       # we're not interested in a user's password if they're
  298       # supplying a key
  299       $r->param("passwd", "");
  300 
  301       if ($self->checkKey($user, $key)) {
  302         # valid key, so succeed.
  303         last VERIFY;
  304       } else {
  305         # invalid key. the login page doesn't propogate the key,
  306         # so we know this is an expired session.
  307         $error = "Your session has timed out due to inactivity. You must login again.";
  308         last VERIFY;
  309       }
  310     }
  311 
  312     #########################################################
  313     # a password was supplied.
  314     #########################################################
  315     if ($passwd) {
  316 
  317       if ($self->checkPassword($user, $passwd)) {
  318         # valid password, so create a new session. (we don't want
  319         # to reuse an old one, duh.)
  320         my $Key = $self->generateKey($user);
  321         eval { $db->deleteKey($user) };
  322         $db->addKey($Key);
  323         $r->param("key", $Key->key());
  324         # also delete the password
  325         $r->param("passwd", "");
  326         last VERIFY;
  327       } else {
  328         # incorrect password. fail.
  329         $error = "Incorrect username or password.";
  330         last VERIFY;
  331       }
  332     }
  333 
  334     # neither a key or a password were supplied.
  335     $error = "You must enter a password."
  336   }
  337 
  338   if (defined $error) {
  339     # authentication failed, store the error message
  340     $r->notes("authen_error",$error);
  341 
  342     # if we got a cookie, it probably has incorrect information in it. so
  343     # we want to get rid of it
  344     if ($cookieUser or $cookieKey) {
  345       #warn "fail with error: killing cookie";
  346       $self->killCookie;
  347     }
  348 
  349     return 0;
  350   } elsif ($failWithoutError) {
  351     # authentication failed, but we don't have any error message to report
  352 
  353     # if we got a cookie, it probably has incorrect information in it. so
  354     # we want to get rid of it
  355     if ($cookieUser or $cookieKey) {
  356       #warn "fail without error: killing cookie";
  357       $self->killCookie;
  358     }
  359 
  360     return 0;
  361   } else {
  362     # autentication succeeded!
  363 
  364     # we send a cookie if any of these conditions are met:
  365     # (a) a cookie was used for authentication
  366     # (b) a cookie was sent but not used for authentication, and the
  367     #     credentials used for authentication were the same as those in
  368     #     the cookie
  369     # (c) the user asked to have a cookie sent and is not a guest user.
  370     my $usedCookie = ($credentialSource eq "cookie") || 0;
  371 
  372     my $unusedCookieMatched = (defined($key) and defined($cookieUser) and defined($cookieKey) and
  373                                 $user eq $cookieUser and $key eq $cookieKey) || 0;
  374     my $userRequestsCookie = ($send_cookie and not $login_practice_user) || 0;
  375     #warn "usedCookie=$usedCookie\n";
  376     #warn "unusedCookieMatched=$unusedCookieMatched\n";
  377     #warn "userRequestsCookie=$userRequestsCookie\n";
  378     if ($usedCookie or $unusedCookieMatched or $userRequestsCookie) {
  379       #warn "succeed: sending cookie";
  380       $self->sendCookie($r->param("user"), $r->param("key"));
  381     } elsif ($cookieUser or $cookieKey) {
  382       # otherwise, we don't want any bad cookies sticking around
  383       #warn "succeed: killing cookie";
  384       $self->killCookie;
  385     }
  386     return 1;
  387   }
  388 
  389   # Whatever you do, don't delete this!
  390   critical($r);
  391   # One time, I deleted it, and my mother broke her back, my cat died, and
  392   # the Pope got a tummy ache. When I replaced the line, I received eternal
  393   # salvation and a check for USD 500.
  394 }
  395 
  396 1;
  397 
  398 __END__
  399 
  400 =head1 AUTHOR
  401 
  402 Written by Dennis Lambe Jr., malsyned (at) math.rochester.edu, and Sam
  403 Hathaway, sh002i (at) math.rochester.edu.
  404 
  405 =cut

aubreyja at gmail dot com
ViewVC Help
Powered by ViewVC 1.0.9