package Bric::App::Auth; =head1 NAME Bric::App::Auth - Does the dirty work of authentication. =head1 VERSION $LastChangedRevision$ =cut # Grab the Version Number. require Bric; our $VERSION = Bric->VERSION; =head1 DATE $LastChangedDate: 2006-03-18 01:26:34 +0100 (Sat, 18 Mar 2006) $ =head1 SYNOPSIS use lib '/usr/local/bricolage/lib'; ErrorLog /usr/local/apache/logs/error_log TransferLog /usr/local/apache/logs/access_log SSLEngine on SSLCipherSuite ALL:!ADH:!EXP56:RC4+RSA:+HIGH:+MEDIUM:+LOW:+SSLv2:+EXP:+eNULL SSLCertificateFile /usr/local/apache/conf/ssl.crt/server.crt SSLCertificateKeyFile /usr/local/apache/conf/ssl.key/server.key SetHandler perl-script PerlHandler Bric::App::Auth =head1 DESCRIPTION This module handles the user authentication. =cut ################################################################################ # Dependencies ################################################################################ # Standard Dependencies use strict; ################################################################################ # Programmatic Dependences use Apache::Constants qw(:common); use Apache::Log; use Bric::Config qw(:auth :cookies); use Bric::App::Session qw(:user); use Bric::App::Cache; use Bric::App::Util qw(:redir); use Bric::Biz::Person::User; use Digest::MD5 qw(md5_hex); use URI::Escape; use base qw( Exporter ); our @EXPORT_OK = qw(auth login logout); our %EXPORT_TAGS = (all => \@EXPORT_OK); ################################################################################ # Inheritance ################################################################################ ################################################################################ # Function and Closure Prototypes ################################################################################ my ($make_cookie, $make_hash, $fail); ################################################################################ # Constants ################################################################################ use constant LOGIN_MARKER_REGEX => qr/${ \LOGIN_MARKER() }/; ################################################################################ # Fields ################################################################################ # Public Class Fields ################################################################################ # Private Class Fields my $ap = 'Bric::Util::Fault::Exception::AP'; my ($c, $cookie_class); ################################################################################ ################################################################################ # Instance Fields BEGIN { $cookie_class = $ENV{MOD_PERL} ? 'Apache::Cookie' : 'CGI::Cookie'; eval "require $cookie_class"; } ################################################################################ # Class Methods ################################################################################ =head1 INTERFACE =head2 Constructors NONE. =head2 Destructors NONE. =head2 Public Class Methods NONE. =head2 Public Functions =over 4 =item my ($res, $msg) = auth($r) Checks to see if the user is logged in to the current session. Used by Bric::App::AccessHandler. B NONE. B NONE. B NONE. =cut sub auth { my $r = shift; return &$fail($r) unless my %cookies = $cookie_class->fetch; return &$fail($r) unless my $cookie = $cookies{&AUTH_COOKIE}; my %val = $cookie->value; return &$fail($r) unless $val{exp} > time; return &$fail($r, 'Malformed cookie.') unless $val{ip} && $val{hash} && $val{user} && $val{exp} && $val{lmu}; # Get the hash for this cookie. my ($hash, $exp, $ip, $lul) = &$make_hash($r, @val{qw(user exp ip lmu)}); if ( $hash ne $val{hash}) { # Oh-oh, someone's been monkeying with the cookie (or maybe the secret # changed?). my $c = $r->connection; $ip = $c->remote_ip; my $host = $c->remote_host; return &$fail($r, "Cookie hash mismatch from $ip (Hostname '$host') " . "for user '$val{user}.'"); } $c ||= Bric::App::Cache->new; my $u = get_user_object(); if ( !$u || ($c->get_lmu_time || 0) > $lul) { # There have been changes to the users. Reload this user from the # database. (my $look = $val{user}) =~ s/([_%\\])/\\$1/g; return &$fail($r, 'User does not exist or is disabled.') unless $u = Bric::Biz::Person::User->lookup({ login => $look }); # Set up the user and expire the user sites from the session. set_user($r, $u); # XXX Wish there were a better place to do this... Bric::App::Session::set_state_data('site_context', 'sites' => 0); $lul = time; } &$make_cookie($r, $val{user}, $lul); } ################################################################################ =item my ($bool, $msg) = login($r, $username, $password) Logs the user into Bricolage, setting the authentication cookie to allow future access that can be checked by a call to auth(). $bool is true on successful login. $bool is undef on failed login, and $msg contains the reason why the login failed. B None. B NONE. B NONE. =cut sub login { my ($r, $un, $pw) = @_; (my $look = $un) =~ s/([_%\\])/\\$1/g; my $u = Bric::Biz::Person::User->lookup({ login => $look }); # Return failure if authentication fails. return (0, 'Invalid username or password. Please try again.') unless $u && $u->chk_password($pw); # Authentication succeeded. Set up session data and the authentication # cookie. set_user($r, $u); my $cookie = &$make_cookie($r, $un, time); # Work around to redirect cookies to second server my $args = $r->args; return $cookie if defined $args and $args =~ LOGIN_MARKER_REGEX; # The presumption is made that any redirect passed to login will properly # terminate a trailing directory with '/', otherwise all bets are off! my $redirect = del_redirect() || '/'; # root if no redirect $redirect .= ($redirect =~ /\?/) ? '&' : '?'; # : ($redirect =~ m|/$|) ? '?' : '/?'; set_redirect($redirect . LOGIN_MARKER . '=' . LOGIN_MARKER); return $cookie; } ################################################################################ =item masquerade($r, $user) Sets up a different user for the current user to masquerade as. This is useful when an administrator needs a to masquerade as another user in order to check in assets that user hasn't checked in. Note that C performs no authentication. It is expected that the current user will have permission to masquerade as the user passed in. B None. B NONE. B NONE. =cut sub masquerade { my ($r, $u) = @_; # Set up session data and the authentication cookie. set_user($r, $u); &$make_cookie($r, $u, time); } ################################################################################ =item my $bool = logout($r) Logs the currently logged-in user out. B None. B NONE. B NONE. =cut sub logout { my $r = shift; my $cookie = $cookie_class->new($r, -name => AUTH_COOKIE, -expires => "-1d", -path => '/', -value => "logout"); $cookie->bake; return 1; } =back =head1 PRIVATE =head2 Private Class Methods NONE. =head2 Private Instance Methods NONE. =head2 Private Functions =over 4 =item my $cookie = &$make_cookie($r, $username) =item my $cookie = &$make_cookie($r, $username, $lul_time) Bakes the authentication cookie. B None. B NONE. B NONE. =cut $make_cookie = sub { my ($r, $un, $lul) = @_; my ($hash, $exp, $ip) = &$make_hash($r, $un, undef, undef, $lul); my @args = ( -name => AUTH_COOKIE, -expires => "+" . AUTH_TTL . "S", -path => '/', -value => { ip => $ip, user => $un, hash => $hash, exp => $exp, lmu => $lul } ); if ($ENV{MOD_PERL}) { my $cookie = $cookie_class->new($r, @args); $cookie->bake; return 1; } else { return $cookie_class->new(@args); } }; =item my ($hash, $exp, $ip) = &$make_hash($r, $un) =item my ($hash, $exp, $ip) = &$make_hash($r, $un, $exp, $ip, $lul) Returns the data points required for baking cookies. These include the MD5 hash, the expiration time, and the IP subnet. B None. B NONE. B NONE. =cut $make_hash = sub { my ($r, $un, $exp, $ip, $lul) = @_; my $time = time; $lul ||= $time; $exp ||= $time + AUTH_TTL; unless ($ip) { $ip = $r->connection->remote_ip; $ip = substr($ip, 0, rindex($ip, '.')); } # work around Perl bug where utf-8 strings result in different md5 # hashes. $exp = "$exp"; $ip = "$ip"; $lul = "$lul"; $un = "$un"; my $hash = md5_hex(AUTH_SECRET . md5_hex(join ':', AUTH_SECRET, $ip, $exp, $un, $lul)); return ($hash, $exp, $ip, $lul); }; =item my ($ret, $msg) = &$fail($r, $msg) Expires the user session and then returns an error message explaining why the user wasn't able to authenticate. B =over 4 =item * Unable to expire user session. =item * Difficulties tie'ing the session hash. =back B NONE. B NONE. =cut $fail = sub { my ($r, $msg) = @_; # Expire the existing session. Bric::App::Session::expire_session($r); # Now create a new session. Bric::App::Session::setup_user_session($r, 1); # Return the failure message. return (0, $msg); }; 1; __END__ =back =head1 NOTES NONE. =head1 AUTHOR David Wheeler =head1 SEE ALSO L =cut