################################################################################
# WeBWorK mod_perl (c) 1995-2002 WeBWorK Team, Univeristy of Rochester
# $Id$
################################################################################

package WeBWorK::Authen;

=head1 NAME

WeBWorK::Authen - Check user identity, manage session keys.

=cut

use strict;
use warnings;
use WeBWorK::DB::Auth;

sub new($$$) {
	my $invocant = shift;
	my $class = ref($invocant) || $invocant;
	my $self = {};
	($self->{r}, $self->{courseEnvironment}) = @_;
	bless $self, $class;
	return $self;
}

sub generate_key {
	# Package constants.  These should never be changed in other places ever
	my $key_length = 40;			# number of chars in each key
	my @key_chars = ('A'..'Z', 'a'..'z', '0'..'9', '.', '^', '/', '!', '*');

	my $i = $key_length;
	my $key = '';
	srand;
	while($i) {
		$key .= $key_chars[rand(@key_chars)];
		$i--;
	}
	return $key;
}

# verify will return 1 if the person is who they say the are.
# If the verification failed because of of invalid authentication data,
# a note will be written in the request explaining why it failed.
# If the request failed because no authentication data was provided, however,
# no note will be written, as this is expected to happen whenever someone
# types in a URL manually, and is not considered an error condition.
sub verify($) {
	my $self = shift;
	my $r = $self->{r};
	my $course_env = $self->{courseEnvironment};
	
	my $user = $r->param('user');
	my $passwd = $r->param('passwd');
	my $key = $r->param('key');
	my $time = time;
	
	# I wanted to get rid of that passwd up here for security reasons,
	# but usability dictates that we not clear out invalid passwords.
	#$r->param('passwd',undef);
	
	my $error;
	my $return;
	
	my $auth = WeBWorK::DB::Auth->new($course_env);
	
	# The first part of this big conditional checks to make that we have
	# all of the form info that we need. It's pretty boring.  The kooky
	# authen stuff comes after that.
	if (!defined $user && !defined $passwd && !defined $key) {
		# The user hasn't even had a chance to say who he is, so we
		# can't hold it against him that we don't know.
		undef $error;
		$return = 0;
	} elsif (!$user) {
		$error = "You must specify a username";
		$return = 0;
	} elsif (!$passwd && !$key) {
		$error = "You must enter a password";
		$return = 0;
	}
	# OK, we're done with the trivia.  Now lets authenticate.
	elsif ($passwd) {
		# A bit of extra logic for practice users
		# Practice users are different because:
		# - They aren't allowed to log in if an active key exists
		#   (except for $debugPracticeUser)
		# - They are allowed to log in with any password
		my $practiceUserPrefix = $course_env->{"practiceUserPrefix"};
		my $debugPracticeUser = $course_env->{"debugPracticeUser"};
		if ($practiceUserPrefix and $user =~ /^$practiceUserPrefix/) {
			if (!$auth->getPassword($user)) { # the only way DB::Auth provides for checking the existence of a user
				$error = "That practice account does not exist";
				$return = 0;
			} elsif ($auth->getKey($user) and $user ne $debugPracticeUser) {
				$error = "That practice account is in use";
				$return = 0;
			} else {
				$key = generate_key;
				$auth->setKey($user, $key);
				$r->param('key',$key);
				$return = 1;
			}
		}
		# Not a practice user.  Do normal authentication.
		elsif ($auth->verifyPassword($user, $passwd)) {
			# Remove the passwd field from subsequent requests.
			$r->param('passwd',undef);
			$key = $auth->getKey($user) || generate_key;
			$auth->setKey($user, $key);
			$r->param('key',$key);
			$return = 1;
		} else {
			$error = "Incorrect username or password";
			$return = 0;
		}
	} elsif ($key) {
		# The timestamp gets updated by verifyKey
		if ($auth->verifyKey($user, $key)) {
			$return = 1;
		} else {
			$error = "Your session has expired.  You must login again";
			$return = 0;
		}
	} else {
		$error = "Unexpected authentication error!";
		$return = 0;
	}

	$r->notes("authen_error",$error) if defined($error);
	return $return;
	
	# Whatever you do, don't delete this!
	critical($r);
}

1;
