Using Basic Auth with WebGUI (#12198)

Per IRC discussion with preaction, make HTTP auth failures soft failures.
Don't attempt to re-auth the user on failure.  Otherwise, .htaccess or
similar put in place to protect a site and WebGUI get into a skirmish
(users are asked to re-auth even if they did the .htaccess correctly,
the log gets flooded, cats get radio shows, etc).
This commit is contained in:
Scott Walters 2011-09-07 20:24:29 -04:00
parent ee121e9460
commit 622391b61d
3 changed files with 73 additions and 71 deletions

View file

@ -54,9 +54,13 @@ These subroutines are available from this package:
#------------------------------------------------------------------- #-------------------------------------------------------------------
=head2 authen ( requestObject, [ user, pass, config ]) =head2 authen ( requestObject, user || undef, pass || undef, session ])
HTTP Basic auth for WebGUI. HTTP Basic auth for WebGUI.
Either called from L<WebGUI::Content::URL> directly or indirectly when pushed back on the L<mod_perl> handler stack.
HTTP Basic auth is an alternative authentication mechanism for WebGUI for robots such as RSS feed readers.
L<WebGUI::Content::URL> does nothing with the return codes from here, but L<mod_perl> uses them if this routine
gets pushed as a handler.
=head3 requestObject =head3 requestObject
@ -70,16 +74,17 @@ The username to authenticate with. Will pull from the request object if not spec
The password to authenticate with. Will pull from the request object if not specified. The password to authenticate with. Will pull from the request object if not specified.
=head3 config =head3 session
A reference to a WebGUI::Config object. One will be created if it isn't specified. A reference to a WebGUI::Session object.
=cut =cut
sub authen { sub authen {
my ($request, $username, $password, $config) = @_; my ($request, $username, $password, $session) = @_;
$request = Apache2::Request->new($request); $request = Apache2::Request->new($request);
my $log = $session->log;
my $server = Apache2::ServerUtil->server; my $server = Apache2::ServerUtil->server;
my $status = Apache2::Const::OK; my $status = Apache2::Const::OK;
@ -88,67 +93,46 @@ sub authen {
if ($request->auth_type eq "Basic") { if ($request->auth_type eq "Basic") {
($status, $password) = $request->get_basic_auth_pw; ($status, $password) = $request->get_basic_auth_pw;
$username = $request->user; $username = $request->user;
$username or return Apache2::Const::HTTP_UNAUTHORIZED;
} }
else { else {
return Apache2::Const::HTTP_UNAUTHORIZED; # per http://www.webgui.org/use/bugs/tracker/12198, failures result in the user remaining visitor, not them
# being denied access entirely.
# $status = Apache2::Const::HTTP_UNAUTHORIZED; # no
return $status;
} }
} }
$config ||= WebGUI::Config->new($server->dir_config('WebguiRoot'),$request->dir_config('WebguiConfig')); my $user = WebGUI::User->newByUsername($session, $username);
my $cookies = eval { APR::Request::Apache2->handle($request)->jar(); }; if ( ! defined $user ) {
if (blessed $@ && $@->isa('APR::Request::Error')) { # $status = Apache2::Const::HTTP_UNAUTHORIZED; # no
$cookies = $@->jar; return $status;
} }
else {
$cookies = {};
}
# determine session id
my $sessionId = $cookies->{$config->getCookieName};
my $session = WebGUI::Session->open($server->dir_config('WebguiRoot'),$config->getFilename, $request, $server, $sessionId);
my $log = $session->log;
$request->pnotes(wgSession => $session);
if (defined $sessionId && $session->user->isRegistered) { # got a session id passed in or from a cookie my $authMethod = $user->authMethod;
$log->info("BASIC AUTH: using cookie"); if ($authMethod) { # we have an auth method, let's try to instantiate
return Apache2::Const::OK; my $auth = eval { WebGUI::Pluggable::instanciate("WebGUI::Auth::".$authMethod, "new", [ $session, $authMethod ] ) };
} if ($@) { # got an error
elsif ($status != Apache2::Const::OK) { # prompt the user for their username and password $log->error($@);
$log->info("BASIC AUTH: prompt for user/pass"); return Apache2::Const::SERVER_ERROR;
return $status; }
} elsif ($auth->authenticate($username, $password)) { # lets try to authenticate
elsif (defined $username && $username ne "") { # no session cookie, let's try to do basic auth $log->info("BASIC AUTH: authenticated successfully");
$log->info("BASIC AUTH: using user/pass"); my $sessionId = $session->db->quickScalar("select sessionId from userSession where userId=?",[$user->userId]);
my $user = WebGUI::User->newByUsername($session, $username); unless (defined $sessionId) { # no existing session found
if (defined $user) { $log->info("BASIC AUTH: creating new session");
my $authMethod = $user->authMethod; $sessionId = $session->id->generate;
if ($authMethod) { # we have an auth method, let's try to instantiate $auth->_logLogin($user->userId, "success (HTTP Basic)");
my $auth = eval { WebGUI::Pluggable::instanciate("WebGUI::Auth::".$authMethod, "new", [ $session, $authMethod ] ) }; }
if ($@) { # got an error $session->{_var} = WebGUI::Session::Var->new($session, $sessionId);
$log->error($@); $session->user({user=>$user});
return Apache2::Const::SERVER_ERROR; return Apache2::Const::OK;
} }
elsif ($auth->authenticate($username, $password)) { # lets try to authenticate }
$log->info("BASIC AUTH: authenticated successfully");
$sessionId = $session->db->quickScalar("select sessionId from userSession where userId=?",[$user->userId]); $log->security($username." failed to login using HTTP Basic Authentication");
unless (defined $sessionId) { # no existing session found # $status = Apache2::Const::HTTP_UNAUTHORIZED; # no
$log->info("BASIC AUTH: creating new session"); return $status;
$sessionId = $session->id->generate;
$auth->_logLogin($user->userId, "success (HTTP Basic)");
}
$session->{_var} = WebGUI::Session::Var->new($session, $sessionId);
$session->user({user=>$user});
return Apache2::Const::OK;
}
}
}
$log->security($username." failed to login using HTTP Basic Authentication");
$request->auth_type('Basic');
$request->note_basic_auth_failure;
return Apache2::Const::HTTP_UNAUTHORIZED;
}
$log->info("BASIC AUTH: skipping");
return Apache2::Const::HTTP_UNAUTHORIZED;
} }
#------------------------------------------------------------------- #-------------------------------------------------------------------
@ -175,18 +159,6 @@ sub handler {
$matchUri =~ s{^$gateway}{/}; $matchUri =~ s{^$gateway}{/};
my $gotMatch = 0; my $gotMatch = 0;
# handle basic auth
my $auth = $request->headers_in->{'Authorization'};
if ($auth =~ m/^Basic/) { # machine oriented
# Get username and password from Apache and hand over to authen
$auth =~ s/Basic //;
authen($request, split(":", MIME::Base64::decode_base64($auth), 2), $config);
}
else { # realm oriented
$request->push_handlers(PerlAuthenHandler => sub { return WebGUI::authen($request, undef, undef, $config)});
}
# url handlers # url handlers
WEBGUI_FATAL: foreach my $handler (@{$config->get("urlHandlers")}) { WEBGUI_FATAL: foreach my $handler (@{$config->get("urlHandlers")}) {
my ($regex) = keys %{$handler}; my ($regex) = keys %{$handler};

View file

@ -64,12 +64,30 @@ to the user, instead of displaying the Page Not Found page.
sub handler { sub handler {
my ($request, $server, $config) = @_; my ($request, $server, $config) = @_;
$request->push_handlers(PerlResponseHandler => sub { $request->push_handlers(PerlResponseHandler => sub {
my $request = shift;
$request = Apache2::Request->new($request);
my $session = $request->pnotes('wgSession'); my $session = $request->pnotes('wgSession');
WEBGUI_FATAL: { WEBGUI_FATAL: {
unless (defined $session) { unless (defined $session) {
$session = WebGUI::Session->open($server->dir_config('WebguiRoot'), $config->getFilename, $request, $server); $session = WebGUI::Session->open($server->dir_config('WebguiRoot'), $config->getFilename, $request, $server);
return Apache2::Const::OK if ! defined $session; return Apache2::Const::OK if ! defined $session;
} }
# if there's no session cookie but there is HTTP auth, try to log in using that
my $auth = $request->headers_in->{'Authorization'};
if( $session->user->isVisitor and $auth ) {
if( $auth =~ m/^Basic/ ) {
$auth =~ s/Basic //;
WebGUI::authen($request, split(":", MIME::Base64::decode_base64($auth), 2), $session);
}
else { # realm oriented
$request->push_handlers(PerlAuthenHandler => sub { return WebGUI::authen($request, undef, undef, $session)});
}
}
WebGUI::Asset::Template->processVariableHeaders($session); WebGUI::Asset::Template->processVariableHeaders($session);
foreach my $handler (@{$config->get("contentHandlers")}) { foreach my $handler (@{$config->get("contentHandlers")}) {
my $output = eval { WebGUI::Pluggable::run($handler, "handler", [ $session ] )}; my $output = eval { WebGUI::Pluggable::run($handler, "handler", [ $session ] )};

View file

@ -56,8 +56,13 @@ my ($mech, $redirect, $response, $url);
# Get the site's base URL # Get the site's base URL
my $baseUrl = 'http://' . $session->config->get('sitename')->[0]; my $baseUrl = 'http://' . $session->config->get('sitename')->[0];
# $baseUrl .= ':8000'; # no easy way to automatically find this
$baseUrl .= $session->config->get('gateway'); $baseUrl .= $session->config->get('gateway');
my $httpAuthUrl = 'http://' . $USERNAME . ':' . $IDENTIFIER . '@' . $session->config->get('sitename')->[0];
# $httpAuthUrl .= ':8000'; # no easy way to automatically find this
$httpAuthUrl .= $session->config->get('gateway');
# Make an asset we can login on # Make an asset we can login on
my $asset my $asset
= $node->addChild({ = $node->addChild({
@ -84,7 +89,7 @@ if ( !$mech->success ) {
plan skip_all => "Cannot load URL '$baseUrl'. Will not test."; plan skip_all => "Cannot load URL '$baseUrl'. Will not test.";
} }
plan tests => 40; # Increment this number for each test you create plan tests => 42; # Increment this number for each test you create
#---------------------------------------------------------------------------- #----------------------------------------------------------------------------
# no form: Test logging in on a normal page sends the user back to the same page # no form: Test logging in on a normal page sends the user back to the same page
@ -276,3 +281,10 @@ $mech->submit_form_ok(
); );
$mech->base_is( $assetUrl, "We don't get redirected" ); $mech->base_is( $assetUrl, "We don't get redirected" );
#----------------------------------------------------------------------------
# HTTP basic auth
$mech = Test::WWW::Mechanize->new;
$mech->get( $httpAuthUrl );
$mech->content_contains( "Hello, $USERNAME", "We are greeted by name" );
$mech->get( $httpAuthUrl . $asset->get('url') );
$mech->content_contains( "ARTICLE", "We are shown the article" );