[system] / branches / rel-2-1-a1 / webwork-modperl / lib / WeBWorK / Authen.pm Repository:
ViewVC logotype

View of /branches/rel-2-1-a1/webwork-modperl/lib/WeBWorK/Authen.pm

Parent Directory Parent Directory | Revision Log Revision Log


Revision 2527 - (download) (as text) (annotate)
Fri Jul 16 20:11:48 2004 UTC (8 years, 11 months ago) by glarose
File size: 15968 byte(s)
Authen.pm modified to allow proctored GatewayQuiz-zes.

Added verifyProctor() routine.

    1 ################################################################################
    2 # WeBWorK Online Homework Delivery System
    3 # Copyright © 2000-2003 The WeBWorK Project, http://openwebwork.sf.net/
    4 # $CVSHeader: webwork2/lib/WeBWorK/Authen.pm,v 1.30 2004/03/15 20:17:35 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 Date::Format;
   29 
   30 use constant COOKIE_LIFESPAN => 60*60*24*30; # 30 days
   31 
   32 sub new {
   33   my ($invocant, $r) = @_;
   34   my $class = ref($invocant) || $invocant;
   35   my $self = {
   36     r => $r,
   37   };
   38   bless $self, $class;
   39   return $self;
   40 }
   41 
   42 sub checkPassword($$$) {
   43   my ($self, $userID, $possibleClearPassword) = @_;
   44   my $db = $self->{r}->db;
   45 
   46   my $Password = $db->getPassword($userID); # checked
   47   return 0 unless defined $Password;
   48 
   49   my $possibleCryptPassword = crypt($possibleClearPassword, $Password->password());
   50   return $possibleCryptPassword eq $Password->password();
   51 }
   52 
   53 sub generateKey($$) {
   54   my ($self, $userID) = @_;
   55   my $ce = $self->{r}->ce;
   56 
   57   my @chars = @{ $ce->{sessionKeyChars} };
   58   my $length = $ce->{sessionKeyLength};
   59 
   60   srand;
   61   my $key = join ("", @chars[map rand(@chars), 1 .. $length]);
   62   return WeBWorK::DB::Record::Key->new(user_id=>$userID, key=>$key, timestamp=>time);
   63 }
   64 
   65 sub checkKey($$$) {
   66   my ($self, $userID, $possibleKey) = @_;
   67   my $ce = $self->{r}->ce;
   68   my $db = $self->{r}->db;
   69 
   70   my $Key = $db->getKey($userID); # checked
   71   return 0 unless defined $Key;
   72   if (time <= $Key->timestamp()+$ce->{sessionKeyTimeout}) {
   73     if ($possibleKey eq $Key->key()) {
   74       # unexpired and matches -- update timestamp
   75       $Key->timestamp(time);
   76       $db->putKey($Key);
   77       return 1;
   78     } else {
   79       # unexpired but doesn't match -- leave timestamp alone
   80       # we do this to keep an attacker from keeping someone's session
   81       # alive. (yeah, we don't match IPs.)
   82       return 0;
   83     }
   84   } else {
   85     # expired -- delete key
   86     $db->deleteKey($userID);
   87     return 0;
   88   }
   89 }
   90 
   91 sub unexpiredKeyExists($$) {
   92   my ($self, $userID) = @_;
   93   my $ce = $self->{r}->ce;
   94   my $db = $self->{r}->db;
   95 
   96   my $Key = $db->getKey($userID); # checked
   97   return 0 unless defined $Key;
   98   if (time <= $Key->timestamp()+$ce->{sessionKeyTimeout}) {
   99     # unexpired, but leave timestamp alone
  100     return 1;
  101   } else {
  102     # expired -- delete key
  103     $db->deleteKey($userID);
  104     return 0;
  105   }
  106 }
  107 
  108 sub fetchCookie {
  109   my ($self, $user, $key) = @_;
  110   my $r = $self->{r};
  111   my $ce = $r->ce;
  112   my $urlpath = $r->urlpath;
  113 
  114   my $courseID = $urlpath->arg("courseID");
  115 
  116   my %cookies = Apache::Cookie->fetch;
  117   my $cookie = $cookies{"WeBWorKCourseAuthen.$courseID"};
  118 
  119   if ($cookie) {
  120     #warn __PACKAGE__, ": fetchCookie: found a cookie for this course: \"", $cookie->as_string, "\"\n";
  121     #warn __PACKAGE__, ": fetchCookie: cookie has this value: \"", $cookie->value, "\"\n";
  122     my ($userID, $key) = split "\t", $cookie->value;
  123     if (defined $userID and defined $key and $userID ne "" and $key ne "") {
  124       #warn __PACKAGE__, ": fetchCookie: looks good, returning userID=$userID key=$key\n";
  125       return $userID, $key;
  126     } else {
  127       #warn __PACKAGE__, ": fetchCookie: malformed cookie. returning empty strings.\n";
  128       return "", "";
  129     }
  130   } else {
  131     #warn __PACKAGE__, ": fetchCookie: found no cookie for this course. returning empty strings.\n";
  132     return "", "";
  133   }
  134 }
  135 
  136 sub sendCookie {
  137   my ($self, $userID, $key) = @_;
  138   my $r = $self->{r};
  139   my $ce = $r->ce;
  140 
  141   my $courseID = $r->urlpath->arg("courseID");
  142 
  143   my $expires = time2str("%a, %d-%h-%Y %H:%M:%S %Z", time+COOKIE_LIFESPAN, "GMT");
  144   my $cookie = Apache::Cookie->new($r,
  145     -name    => "WeBWorKCourseAuthen.$courseID",
  146     -value   => "$userID\t$key",
  147     -expires => $expires,
  148     -domain  => $r->hostname,
  149     -path    => $ce->{webworkURLRoot},
  150     -secure  => 0,
  151   );
  152   my $cookieString = $cookie->as_string;
  153 
  154   #warn __PACKAGE__, ": sendCookie: about to add Set-Cookie header with this string: \"", $cookie->as_string, "\"\n";
  155   $r->headers_out->set("Set-Cookie" => $cookie->as_string);
  156 }
  157 
  158 sub killCookie {
  159   my ($self) = @_;
  160   my $r = $self->{r};
  161   my $ce = $r->ce;
  162 
  163   my $courseID = $r->urlpath->arg("courseID");
  164 
  165   my $expires = time2str("%a, %d-%h-%Y %H:%M:%S %Z", time-60*60*24, "GMT");
  166   my $cookie = Apache::Cookie->new($r,
  167     -name => "WeBWorKCourseAuthen.$courseID",
  168     -value => "\t",
  169     -expires => $expires,
  170     -domain => $r->hostname,
  171     -path => $ce->{webworkURLRoot},
  172     -secure => 0,
  173   );
  174   my $cookieString = $cookie->as_string;
  175 
  176   #warn __PACKAGE__, ": killCookie: about to add Set-Cookie header with this string: \"", $cookie->as_string, "\"\n";
  177   $r->headers_out->set("Set-Cookie" => $cookie->as_string);
  178 }
  179 
  180 # verify will return 1 if the person is who they say the are. If the
  181 # verification failed because of of invalid authentication data, a note will be
  182 # written in the request explaining why it failed. If the request failed because
  183 # no authentication data was provided, however, no note will be written, as this
  184 # is expected to happen whenever someone types in a URL manually, and is not
  185 # considered an error condition.
  186 sub verify($) {
  187   my $self = shift;
  188   my $r = $self->{r};
  189   my $ce = $r->ce;
  190   my $db = $r->db;
  191 
  192   my $practiceUserPrefix = $ce->{practiceUserPrefix};
  193   my $debugPracticeUser = $ce->{debugPracticeUser};
  194 
  195   my $force_passwd_authen = $r->param('force_passwd_authen');
  196   my $login_practice_user = $r->param('login_practice_user');
  197   my $send_cookie = $r->param("send_cookie");
  198 
  199   my $error;
  200   my $failWithoutError = 0;
  201   my $credentialSource = "params";
  202 
  203   my $user = $r->param('user');
  204   my $passwd = $r->param('passwd');
  205   my $key = $r->param('key');
  206 
  207   my ($cookieUser, $cookieKey) = $self->fetchCookie;
  208   #warn __PACKAGE__, ": verify: cookieUser=$cookieUser cookieKey=$cookieKey\n";
  209 
  210   VERIFY: {
  211     # This block is here so we can "last" out of it when we've
  212     # decided whether we're going to succeed or fail.
  213 
  214     if ($login_practice_user) {
  215       # ignore everything else, find an unused practice user
  216       my $found = 0;
  217       foreach my $userID (sort grep m/^$practiceUserPrefix/, $db->listUsers) {
  218         if (not $self->unexpiredKeyExists($userID)) {
  219           my $Key = $self->generateKey($userID);
  220           $db->addKey($Key);
  221           $r->param("user", $userID);
  222           $r->param("key", $Key->key);
  223           $found = 1;
  224           last;
  225         }
  226       }
  227       unless ($found) {
  228         $error = "No practice users are available. Please try again in a few minutes.";
  229       }
  230       last VERIFY;
  231     }
  232 
  233     # no authentication data was given. this is OK.
  234     unless (defined $user or defined $passwd or defined $key) {
  235       # check to see if a cookie was sent by the browser. if so, use the
  236       # user and key from the cookie for authentication. note that the
  237       # cookie is only used if no credentials are sent as parameters.
  238       if ($cookieUser and $cookieKey) {
  239         $user = $cookieUser;
  240         $key = $cookieKey;
  241         $r->param("user", $user);
  242         $r->param("key", $key);
  243         $credentialSource = "cookie";
  244       } else {
  245         $failWithoutError = 1;
  246         last VERIFY;
  247       }
  248     }
  249 
  250     if (defined $user and $force_passwd_authen) {
  251       $failWithoutError = 1;
  252       last VERIFY;
  253     }
  254 
  255     # no user was supplied.  somebody's building their own GET
  256     unless ($user) {
  257       $error = "You must specify a username.";
  258       last VERIFY;
  259     }
  260     ########################################################
  261     # Make sure user is in the database
  262     ########################################################
  263 
  264     my $userRecord    =   $db->getUser($user);
  265     unless (defined $userRecord) { # checked
  266       $error = "There is no account for $user in this course.";
  267       last VERIFY;
  268     }
  269     ########################################################
  270     # Make sure the user's status is defined.
  271     ########################################################
  272     unless (defined $userRecord->status) {
  273       $userRecord-> status('C');
  274       #warn "Setting status for user $user to C.  It was previously undefined.";
  275     }
  276     unless ($userRecord->status eq 'C') {
  277       $error  = "The user $user has been dropped from this course. ";
  278       last VERIFY;
  279 
  280     }
  281     ########################################################
  282     # it's a practice user.
  283     ########################################################
  284     if ($practiceUserPrefix and $user =~ /^$practiceUserPrefix/) {
  285       # we're not interested in a practice user's password
  286       $r->param("passwd", "");
  287 
  288 
  289       # we've got a key.
  290       if ($key) {
  291         if ($self->checkKey($user, $key)) {
  292           # they key was valid.
  293           last VERIFY;
  294         } else {
  295           # the key was invalid.
  296           $error = "Your session has timed out due to inactivity. You must login again.";
  297           last VERIFY;
  298         }
  299       }
  300 
  301       # -- here we know that a key was not supplied. --
  302 
  303       # it's the debug user.
  304       if ($debugPracticeUser and $user eq $debugPracticeUser) {
  305         # clobber any existing session, valid or not.
  306         my $Key = $self->generateKey($user);
  307         eval { $db->deleteKey($user) };
  308         $db->addKey($Key);
  309         $r->param("key", $Key->key());
  310         last VERIFY;
  311       }
  312 
  313       # an unexpired key exists -- the account is in use.
  314       if ($self->unexpiredKeyExists($user)) {
  315         $error = "That practice account is in use.";
  316         last VERIFY;
  317       }
  318 
  319       # here we know the account is not in use, so we
  320       # generate a new  session key (unexpiredKeyExists
  321       # deleted any expired key) and succeed!
  322       my $Key = $self->generateKey($user);
  323       $db->addKey($Key);
  324       $r->param("key", $Key->key());
  325       last VERIFY;
  326     }
  327 
  328     # -- here we know it's a regular user. --
  329 
  330     #########################################################
  331     # Fail with error message if status is D or dropped
  332     #########################################################
  333     if ($db->getUser($user)->status eq 'D' or $db->getUser($user)->status eq 'DROPPED') {
  334       $error  = "The user $user has been dropped from this course. Please contact
  335       your instructor if this is an error.";
  336       last VERIFY;
  337 
  338     }
  339     # a key was supplied.
  340     if ($key) {
  341       # we're not interested in a user's password if they're
  342       # supplying a key
  343       $r->param("passwd", "");
  344 
  345       if ($self->checkKey($user, $key)) {
  346         # valid key, so succeed.
  347         last VERIFY;
  348       } else {
  349         # invalid key. the login page doesn't propogate the key,
  350         # so we know this is an expired session.
  351         $error = "Your session has timed out due to inactivity. You must login again.";
  352         last VERIFY;
  353       }
  354     }
  355 
  356     #########################################################
  357     # a password was supplied.
  358     #########################################################
  359     if ($passwd) {
  360 
  361       if ($self->checkPassword($user, $passwd)) {
  362         # valid password, so create a new session. (we don't want
  363         # to reuse an old one, duh.)
  364         my $Key = $self->generateKey($user);
  365         eval { $db->deleteKey($user) };
  366         $db->addKey($Key);
  367         $r->param("key", $Key->key());
  368         # also delete the password
  369         $r->param("passwd", "");
  370         last VERIFY;
  371       } else {
  372         # incorrect password. fail.
  373         $error = "Incorrect username or password.";
  374         last VERIFY;
  375       }
  376     }
  377 
  378     # neither a key or a password were supplied.
  379     $error = "You must enter a password."
  380   }
  381 
  382   if (defined $error) {
  383     # authentication failed, store the error message
  384     $r->notes("authen_error",$error);
  385 
  386     # if we got a cookie, it probably has incorrect information in it. so
  387     # we want to get rid of it
  388     if ($cookieUser or $cookieKey) {
  389       #warn "fail with error: killing cookie";
  390       $self->killCookie;
  391     }
  392 
  393     return 0;
  394   } elsif ($failWithoutError) {
  395     # authentication failed, but we don't have any error message to report
  396 
  397     # if we got a cookie, it probably has incorrect information in it. so
  398     # we want to get rid of it
  399     if ($cookieUser or $cookieKey) {
  400       #warn "fail without error: killing cookie";
  401       $self->killCookie;
  402     }
  403 
  404     return 0;
  405   } else {
  406     # autentication succeeded!
  407 
  408     # we send a cookie if any of these conditions are met:
  409     # (a) a cookie was used for authentication
  410     # (b) a cookie was sent but not used for authentication, and the
  411     #     credentials used for authentication were the same as those in
  412     #     the cookie
  413     # (c) the user asked to have a cookie sent and is not a guest user.
  414     my $usedCookie = ($credentialSource eq "cookie") || 0;
  415 
  416     my $unusedCookieMatched = (defined($key) and defined($cookieUser) and defined($cookieKey) and
  417                                 $user eq $cookieUser and $key eq $cookieKey) || 0;
  418     my $userRequestsCookie = ($send_cookie and not $login_practice_user) || 0;
  419     #warn "usedCookie=$usedCookie\n";
  420     #warn "unusedCookieMatched=$unusedCookieMatched\n";
  421     #warn "userRequestsCookie=$userRequestsCookie\n";
  422     if ($usedCookie or $unusedCookieMatched or $userRequestsCookie) {
  423       #warn "succeed: sending cookie";
  424       $self->sendCookie($r->param("user"), $r->param("key"));
  425     } elsif ($cookieUser or $cookieKey) {
  426       # otherwise, we don't want any bad cookies sticking around
  427       #warn "succeed: killing cookie";
  428       $self->killCookie;
  429     }
  430     return 1;
  431   }
  432 
  433   # Whatever you do, don't delete this!
  434   critical($r);
  435   # One time, I deleted it, and my mother broke her back, my cat died, and
  436   # the Pope got a tummy ache. When I replaced the line, I received eternal
  437   # salvation and a check for USD 500.
  438 }
  439 
  440 sub verifyProctor ($) {
  441     my $self = shift();
  442     my $r = $self->{r};
  443     my $ce = $r->ce;
  444     my $db = $r->db;
  445 
  446     my $user =          $r->param('user');
  447     my $proctorUser =   $r->param('proctor_user');
  448     my $proctorPasswd = $r->param('proctor_passwd');
  449     my $proctorKey =    $r->param('proctor_key');
  450 
  451     my $failWithoutError = 0;
  452     my $error = '';
  453 
  454   VERIFY: {
  455       unless( ( defined($proctorUser) && $proctorUser ) or
  456         ( defined($proctorPasswd) && $proctorPasswd ) or
  457         ( defined($proctorKey) && $proctorKey ) ) {
  458     $failWithoutError = 1;
  459     last VERIFY;
  460       }
  461 
  462       unless( defined($proctorUser) ) {
  463     $error = 'Proctor username must be specified.';
  464     last VERIFY;
  465       }
  466 
  467       my $proctorUserRecord = $db->getUser( $proctorUser );
  468       unless( defined( $proctorUserRecord ) ) {
  469     $error = "There is no proctor account for $proctorUser in this course";
  470     last VERIFY;
  471       }
  472 
  473       unless( ! defined( $proctorUserRecord->status() ) ||
  474         $proctorUserRecord->status() eq 'C' ) {
  475     $error = "Proctor user $proctorUser does not have a valid status " .
  476         "in this course.";
  477     last VERIFY;
  478       }
  479 
  480       if ( $proctorKey ) {
  481     $r->param( 'proctor_password', '' );
  482 
  483     if ( $self->checkKey("$user,$proctorUser", $proctorKey) ) {
  484         last VERIFY;
  485     } else {
  486         $error = "Invalid or expired proctor session key.";
  487         last VERIFY;
  488     }
  489       }
  490 
  491       if ( $proctorPasswd ) {
  492 
  493     if ( $self->checkPassword( $proctorUser, $proctorPasswd ) ) {
  494         my $newKeyObject = $self->generateKey( "$user,$proctorUser" );
  495         $r->param('proctor_passwd', '');
  496 
  497         eval{ $db->deleteKey( "$user,$proctorUser" ); };
  498         $db->addKey($newKeyObject);
  499 
  500         $r->param('proctor_key', $newKeyObject->key());
  501 
  502         last VERIFY;
  503     }  else {
  504         $error = 'Incorrect proctor username or password.';
  505         last VERIFY;
  506     }
  507       }
  508   }
  509 
  510     if ( defined($error) && $error ) {
  511   $r->notes("authen_error", $error);
  512   return 0;
  513 
  514     } elsif ( $failWithoutError ) {
  515   return 0;
  516 
  517     } else {
  518   return 1;
  519     }
  520     critical($r);  # where does critical() come from?
  521 }
  522 
  523 1;
  524 
  525 __END__
  526 
  527 =head1 AUTHOR
  528 
  529 Written by Dennis Lambe Jr., malsyned (at) math.rochester.edu, and Sam
  530 Hathaway, sh002i (at) math.rochester.edu.
  531 
  532 =cut

aubreyja at gmail dot com
ViewVC Help
Powered by ViewVC 1.0.9