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.
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
@ -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.
=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
sub authen {
my ($request, $username, $password, $config) = @_;
my ($request, $username, $password, $session) = @_;
$request = Apache2::Request->new($request);
my $log = $session->log;
my $server = Apache2::ServerUtil->server;
my $status = Apache2::Const::OK;
@ -88,67 +93,46 @@ sub authen {
if ($request->auth_type eq "Basic") {
($status, $password) = $request->get_basic_auth_pw;
$username = $request->user;
$username or return Apache2::Const::HTTP_UNAUTHORIZED;
}
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 $cookies = eval { APR::Request::Apache2->handle($request)->jar(); };
if (blessed $@ && $@->isa('APR::Request::Error')) {
$cookies = $@->jar;
my $user = WebGUI::User->newByUsername($session, $username);
if ( ! defined $user ) {
# $status = Apache2::Const::HTTP_UNAUTHORIZED; # no
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
$log->info("BASIC AUTH: using cookie");
return Apache2::Const::OK;
}
elsif ($status != Apache2::Const::OK) { # prompt the user for their username and password
$log->info("BASIC AUTH: prompt for user/pass");
return $status;
}
elsif (defined $username && $username ne "") { # no session cookie, let's try to do basic auth
$log->info("BASIC AUTH: using user/pass");
my $user = WebGUI::User->newByUsername($session, $username);
if (defined $user) {
my $authMethod = $user->authMethod;
if ($authMethod) { # we have an auth method, let's try to instantiate
my $auth = eval { WebGUI::Pluggable::instanciate("WebGUI::Auth::".$authMethod, "new", [ $session, $authMethod ] ) };
if ($@) { # got an error
$log->error($@);
return Apache2::Const::SERVER_ERROR;
}
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]);
unless (defined $sessionId) { # no existing session found
$log->info("BASIC AUTH: creating new session");
$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;
my $authMethod = $user->authMethod;
if ($authMethod) { # we have an auth method, let's try to instantiate
my $auth = eval { WebGUI::Pluggable::instanciate("WebGUI::Auth::".$authMethod, "new", [ $session, $authMethod ] ) };
if ($@) { # got an error
$log->error($@);
return Apache2::Const::SERVER_ERROR;
}
elsif ($auth->authenticate($username, $password)) { # lets try to authenticate
$log->info("BASIC AUTH: authenticated successfully");
my $sessionId = $session->db->quickScalar("select sessionId from userSession where userId=?",[$user->userId]);
unless (defined $sessionId) { # no existing session found
$log->info("BASIC AUTH: creating new session");
$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");
# $status = Apache2::Const::HTTP_UNAUTHORIZED; # no
return $status;
}
#-------------------------------------------------------------------
@ -175,18 +159,6 @@ sub handler {
$matchUri =~ s{^$gateway}{/};
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
WEBGUI_FATAL: foreach my $handler (@{$config->get("urlHandlers")}) {
my ($regex) = keys %{$handler};

View file

@ -64,12 +64,30 @@ to the user, instead of displaying the Page Not Found page.
sub handler {
my ($request, $server, $config) = @_;
$request->push_handlers(PerlResponseHandler => sub {
my $request = shift;
$request = Apache2::Request->new($request);
my $session = $request->pnotes('wgSession');
WEBGUI_FATAL: {
unless (defined $session) {
$session = WebGUI::Session->open($server->dir_config('WebguiRoot'), $config->getFilename, $request, $server);
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);
foreach my $handler (@{$config->get("contentHandlers")}) {
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
my $baseUrl = 'http://' . $session->config->get('sitename')->[0];
# $baseUrl .= ':8000'; # no easy way to automatically find this
$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
my $asset
= $node->addChild({
@ -84,7 +89,7 @@ if ( !$mech->success ) {
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
@ -276,3 +281,10 @@ $mech->submit_form_ok(
);
$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" );