From 622391b61d29f91eb8e12fed9e63870a84f968dd Mon Sep 17 00:00:00 2001 From: Scott Walters Date: Wed, 7 Sep 2011 20:24:29 -0400 Subject: [PATCH] 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). --- lib/WebGUI.pm | 112 ++++++++++++++------------------------ lib/WebGUI/URL/Content.pm | 18 ++++++ t/Auth/mech.t | 14 ++++- 3 files changed, 73 insertions(+), 71 deletions(-) diff --git a/lib/WebGUI.pm b/lib/WebGUI.pm index faae3fdde..ae7f2fce1 100644 --- a/lib/WebGUI.pm +++ b/lib/WebGUI.pm @@ -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 directly or indirectly when pushed back on the L handler stack. +HTTP Basic auth is an alternative authentication mechanism for WebGUI for robots such as RSS feed readers. +L does nothing with the return codes from here, but L 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}; diff --git a/lib/WebGUI/URL/Content.pm b/lib/WebGUI/URL/Content.pm index 8c24ba8b6..a3f56da1e 100644 --- a/lib/WebGUI/URL/Content.pm +++ b/lib/WebGUI/URL/Content.pm @@ -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 ] )}; diff --git a/t/Auth/mech.t b/t/Auth/mech.t index 3f461794e..8464da41a 100644 --- a/t/Auth/mech.t +++ b/t/Auth/mech.t @@ -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" );