CVE-2011-1007: Credentials Management Errors (CWE-255)
value: Best Practical Solutions RT before 3.8.9 does not perform certain redirect actions upon a login, which allows physically proximate attackers to obtain credentials by resubmitting the login form via the back button of a web browser on an unattended workstation after an RT logout.
CWE description: Weaknesses in this category are related to the management of credentials.
Published: 2011-02-28T16:00Z
Repository: https://github.com/bestpractical/rt
Language: Other
Files changed: lib/RT/Interface/Web.pm, share/html/Elements/ListActions, share/html/Elements/Login, share/html/NoAuth/Login.html
Commit date: 2010-10-22 17:23:43 -0400
Commit message: Redirect to the desired page after logging in a user

This prevents back button attacks in the form of resubmitting form data
after the user has logged out of the browser (but not closed it).  See
also rt3 #15804.

For non-homepage hits, the browser now also gets redirected to
/NoAuth/Login.html to get a login form.

We use the session to store the next page URL (referenced by a hash),
similar to how action results work.

Diff: diff --git a/lib/RT/Interface/Web.pm b/lib/RT/Interface/Web.pm
index 29deaa477..255f763d8 100755
--- a/lib/RT/Interface/Web.pm
+++ b/lib/RT/Interface/Web.pm
@@ -205,17 +205,34 @@ sub HandleRequest {
     $HTML::Mason::Commands::m->callback( %$ARGS, CallbackName => 'Auth', CallbackPage => '/autohandler' );
 
     unless ( _UserLoggedIn() ) {
         _ForceLogout();
 
-        # If the user is logging in, let's authenticate
-        if ( defined $ARGS->{user} && defined $ARGS->{pass} ) {
-            AttemptPasswordAuthentication($ARGS);
-        } else {
-            # if no credentials then show him login page
-            $HTML::Mason::Commands::m->comp( '/Elements/Login', %$ARGS );
-            $HTML::Mason::Commands::m->abort;
+        # Authenticate if the user is trying to login via user/pass query args
+        my ($authed, $msg) = AttemptPasswordAuthentication($ARGS);
+
+        unless ($authed) {
+            my $m = $HTML::Mason::Commands::m;
+            my $results = $msg ? LoginError($msg) : undef;
+
+            # REST urls get a special 401 response
+            if ($m->request_comp->path =~ '^/REST/\d+\.\d+/') {
+                $HTML::Mason::Commands::r->content_type("text/plain");
+                $m->error_format("text");
+                $m->out("RT/$RT::VERSION 401 Credentials required\n");
+                $m->out("\n$msg\n") if $msg;
+                $m->abort;
+            }
+            # Specially handle /index.html so that we get a nicer URL
+            elsif ( $m->request_comp->path eq '/index.html' ) {
+                my $next = SetNextPage(RT->Config->Get('WebURL'));
+                $m->comp('/NoAuth/Login.html', next => $next, results => $results);
+                $m->abort;
+            }
+            else {
+                TangentForLogin(results => $results);
+            }
         }
     }
 
     # now it applies not only to home page, but any dashboard that can be used as a workspace
     $HTML::Mason::Commands::session{'home_refresh_interval'} = $ARGS->{'HomeRefreshInterval'}
@@ -243,10 +260,94 @@ sub _UserLoggedIn {
         return undef;
     }
 
 }
 
+=head2 LoginError ERROR
+
+Pushes a login error into the Actions session store and returns the hash key.
+
+=cut
+
+sub LoginError {
+    my $new = shift;
+    my $key = Digest::MD5::md5_hex( rand(1024) );
+    push @{ $HTML::Mason::Commands::session{"Actions"}->{$key} ||= [] }, $new;
+    $HTML::Mason::Commands::session{'i'}++;
+    return $key;
+}
+
+=head2 SetNextPage [PATH]
+
+Intuits and stashes the next page in the sesssion hash.  If PATH is
+specified, uses that instead of the value of L<IntuitNextPage()>.  Returns
+the hash value.
+
+=cut
+
+sub SetNextPage {
+    my $next = shift || IntuitNextPage();
+    my $hash = Digest::MD5::md5_hex( $next . $$ );
+
+    $HTML::Mason::Commands::session{'NextPage'}->{$hash} = $next;
+    $HTML::Mason::Commands::session{'i'}++;  # Cargo culted, do we need this?
+    
+    SendSessionCookie();
+    return $hash;
+}
+
+
+=head2 TangentForLogin [HASH]
+
+Redirects to C</NoAuth/Login.html>, setting the value of L<IntuitNextPage> as
+the next page.  Optionally takes a hash which is dumped into query params.
+
+=cut
+
+sub TangentForLogin {
+    my $hash  = SetNextPage();
+    my %query = (@_, next => $hash);
+    my $login = RT->Config->Get('WebURL') . 'NoAuth/Login.html?';
+    $login .= $HTML::Mason::Commands::m->comp('/Elements/QueryString', %query);
+    Redirect($login);
+}
+
+=head2 IntuitNextPage
+
+Attempt to figure out the path to which we should return the user after a
+tangent.  The current request URL is used, or failing that, the C<WebURL>
+configuration variable.
+
+=cut
+
+sub IntuitNextPage {
+    my $req_uri;
+
+    # This includes any query parameters.  Redirect will take care of making
+    # it an absolute URL.
+    $req_uri = $ENV{'REQUEST_URI'} if $ENV{'REQUEST_URI'};
+
+    my $next = defined $req_uri ? $req_uri : RT->Config->Get('WebURL');
+
+    # sanitize $next
+    my $uri = URI->new($next);
+
+    # You get undef scheme with a relative uri like "/Search/Build.html"
+    unless (!defined($uri->scheme) || $uri->scheme eq 'http' || $uri->scheme eq 'https') {
+        $next = RT->Config->Get('WebURL');
+    }
+
+    # Make sure we're logging in to the same domain
+    # You can get an undef authority with a relative uri like "index.html"
+    my $uri_base_url = URI->new(RT->Config->Get('WebBaseURL'));
+    unless (!defined($uri->authority) || $uri->authority eq $uri_base_url->authority) {
+        $next = RT->Config->Get('WebURL');
+    }
+
+    return $next;
+}
+
 =head2 MaybeShowInstallModePage 
 
 This function, called exclusively by RT's autohandler, dispatches
 a request to RT's Installation workflow, only if Install Mode is enabled in the configuration file.
 
@@ -282,10 +383,14 @@ sub MaybeShowNoAuthPage {
 
     my $m = $HTML::Mason::Commands::m;
 
     return unless $m->base_comp->path =~ RT->Config->Get('WebNoAuthRegex');
 
+    # Don't show the login page to logged in users
+    Redirect(RT->Config->Get('WebURL'))
+        if $m->base_comp->path eq '/NoAuth/Login.html' and _UserLoggedIn();
+
     # If it's a noauth file, don't ask for auth.
     SendSessionCookie();
     $m->comp( { base_comp => $m->request_comp }, $m->fetch_next, %$ARGS );
     $m->abort;
 }
@@ -384,32 +489,42 @@ sub AttemptExternalAuth {
                 $HTML::Mason::Commands::session{'CurrentUser'}->Load($user);
             } else {
 
                 # we failed to successfully create the user. abort abort abort.
                 delete $HTML::Mason::Commands::session{'CurrentUser'};
-                $m->comp( '/Elements/Login', %$ARGS, Error => HTML::Mason::Commands::loc( 'Cannot create user: [_1]', $msg ) )
-                    if RT->Config->Get('WebFallbackToInternalAuth');;
-                $m->abort();
+
+                if (RT->Config->Get('WebFallbackToInternalAuth')) {
+                    my $key = LoginError(
+                        HTML::Mason::Commands::loc('Cannot create user: [_1]', $msg)
+                    );
+                    TangentForLogin( results => $key );
+                } else {
+                    $m->abort();
+                }
             }
         }
 
         if ( _UserLoggedIn() ) {
             $m->callback( %$ARGS, CallbackName => 'ExternalAuthSuccessfulLogin', CallbackPage => '/autohandler' );
         } else {
             delete $HTML::Mason::Commands::session{'CurrentUser'};
             $user = $orig_user;
 
             if ( RT->Config->Get('WebExternalOnly') ) {
-                $m->comp( '/Elements/Login', %$ARGS, Error => HTML::Mason::Commands::loc('You are not an authorized user') );
-                $m->abort();
+                my $key = LoginError(
+                    HTML::Mason::Commands::loc('You are not an authorized user')
+                );
+                TangentForLogin( results => $key );
             }
         }
     } elsif ( RT->Config->Get('WebFallbackToInternalAuth') ) {
         unless ( defined $HTML::Mason::Commands::session{'CurrentUser'} ) {
             # XXX unreachable due to prior defaulting in HandleRequest (check c34d108)
-            $m->comp( '/Elements/Login', %$ARGS, Error => HTML::Mason::Commands::loc('You are not an authorized user') );
-            $m->abort();
+            my $key = LoginError(
+                HTML::Mason::Commands::loc('You are not an authorized user')
+            );
+            TangentForLogin( results => $key );
         }
     } else {
 
         # WebExternalAuth is set, but we don't have a REMOTE_USER. abort
         # XXX: we must return AUTH_REQUIRED status or we fallback to
@@ -418,27 +533,48 @@ sub AttemptExternalAuth {
             if defined $HTML::Mason::Commands::session{'CurrentUser'};
     }
 }
 
 sub AttemptPasswordAuthentication {
-    my $ARGS     = shift;
+    my $ARGS = shift;
+    return unless defined $ARGS->{user} && defined $ARGS->{pass};
+
     my $user_obj = RT::CurrentUser->new();
     $user_obj->Load( $ARGS->{user} );
 
     my $m = $HTML::Mason::Commands::m;
 
     unless ( $user_obj->id && $user_obj->IsPassword( $ARGS->{pass} ) ) {
         $RT::Logger->error("FAILED LOGIN for @{[$ARGS->{user}]} from $ENV{'REMOTE_ADDR'}");
-        $m->comp( '/Elements/Login', %$ARGS, Error => HTML::Mason::Commands::loc('Your username or password is incorrect'), );
         $m->callback( %$ARGS, CallbackName => 'FailedLogin', CallbackPage => '/autohandler' );
-        $m->abort;
+        return (0, HTML::Mason::Commands::loc('Your username or password is incorrect'));
     }
+    else {
+        $RT::Logger->info("Successful login for @{[$ARGS->{user}]} from $ENV{'REMOTE_ADDR'}");
+
+        # It's important to nab the next page from the session before we blow
+        # the session away
+        my $next = $HTML::Mason::Commands::session{'NextPage'}->{$ARGS->{'next'} || ''};
 
-    $RT::Logger->info("Successful login for @{[$ARGS->{user}]} from $ENV{'REMOTE_ADDR'}");
-    InstantiateNewSession();
-    $HTML::Mason::Commands::session{'CurrentUser'} = $user_obj;
-    $m->callback( %$ARGS, CallbackName => 'SuccessfulLogin', CallbackPage => '/autohandler' );
+        InstantiateNewSession();
+        $HTML::Mason::Commands::session{'CurrentUser'} = $user_obj;
+        SendSessionCookie();
+
+        $m->callback( %$ARGS, CallbackName => 'SuccessfulLogin', CallbackPage => '/autohandler' );
+
+        # Really the only time we don't want to redirect here is if we were
+        # passed user and pass as query params in the URL.
+        if ($next) {
+            Redirect($next);
+        }
+        elsif ($ARGS->{'next'}) {
+            # Invalid hash, but still wants to go somewhere, take them to /
+            Redirect(RT->Config->Get('WebURL'));
+        }
+
+        return (1, HTML::Mason::Commands::loc('Logged in'));
+    }
 }
 
 =head2 LoadSessionFromCookie
 
 Load or setup a session cookie for the current user.
@@ -501,10 +637,14 @@ a cached DBI statement handle twice at the same time.
 sub Redirect {
     my $redir_to = shift;
     untie $HTML::Mason::Commands::session;
     my $uri        = URI->new($redir_to);
     my $server_uri = URI->new( RT->Config->Get('WebURL') );
+    
+    # Make relative URIs absolute from the server host and scheme
+    $uri->scheme($server_uri->scheme) if not defined $uri->scheme;
+    $uri->host($server_uri->host)     if not defined $uri->host;
 
     # If the user is coming in via a non-canonical
     # hostname, don't redirect them to the canonical host,
     # it will just upset them (and invalidate their credentials)
     # don't do this if $RT::CanoniaclRedirectURLs is true
diff --git a/share/html/Elements/ListActions b/share/html/Elements/ListActions
index de7584b7a..1e76bfe25 100755
--- a/share/html/Elements/ListActions
+++ b/share/html/Elements/ListActions
@@ -44,11 +44,11 @@
 %# works based on those contributions, and sublicense and distribute
 %# those contributions and any derivatives thereof.
 %# 
 %# END BPS TAGGED BLOCK }}}
 <div class="results">
-<&| /Widgets/TitleBox, title => loc('Results') &>
+<&| /Widgets/TitleBox, title => loc('Results'), %{$titlebox || {}} &>
   <ul class="action-results">
 % foreach my $action (@actions) {
     <li><%$action%></li>
 % }
   </ul>
@@ -88,7 +88,8 @@ if ($actions_pointer &&  ref( $session{'Actions'}->{$actions_pointer} ) eq 'ARRA
 
 return unless @actions;
 
 </%init>
 <%ARGS>
+$titlebox => {}
 @actions => undef
 </%ARGS>
diff --git a/share/html/Elements/Login b/share/html/Elements/Login
index 00a8ab80e..a7820c3bd 100755
--- a/share/html/Elements/Login
+++ b/share/html/Elements/Login
@@ -43,67 +43,32 @@
 %# royalty-free, perpetual, license to use, copy, create derivative
 %# works based on those contributions, and sublicense and distribute
 %# those contributions and any derivatives thereof.
 %# 
 %# END BPS TAGGED BLOCK }}}
-<%INIT>
-if ($m->request_comp->path =~ '^/REST/\d+\.\d+/') {
-    $r->content_type("text/plain");
-    $m->error_format("text");
-    $m->out("RT/$RT::VERSION 401 Credentials required\n");
-    $m->out("\n$Error\n") if $Error;
-    $m->abort;
-}
-
-my $req_uri;
-
-if (UNIVERSAL::can($r, 'uri') and $r->uri =~ m{.*/(.*)}) {
-    $req_uri = $1;
-}
-
-my $form_action = defined $goto             ? $goto
-                : defined $req_uri          ? $req_uri
-                :                             RT->Config->Get('WebPath')
-                ;
-
-# sanitize $form_action
-my $uri = URI->new($form_action);
-
-# You get undef scheme with a relative uri like "/Search/Build.html"
-unless (!defined($uri->scheme) || $uri->scheme eq 'http' || $uri->scheme eq 'https') {
-    $form_action = RT->Config->Get('WebPath');
-}
-
-# Make sure we're logging in to the same domain
-# You can get an undef authority with a relative uri like "index.html"
-my $uri_base_url = URI->new(RT->Config->Get('WebBaseURL'));
-unless (!defined($uri->authority) || $uri->authority eq $uri_base_url->authority) {
-    $form_action = RT->Config->Get('WebPath');
-}
-</%INIT>
-
 % $m->callback( %ARGS, CallbackName => 'Header' );
 <& /Elements/Header, Title => loc('Login'), Focus => 'user' &>
 
 
 %# End of div#quickbar from /Elements/Header
 </div>
 
 <div id="body" class="login-body">
-% if ($Error) {
-<&| "/Widgets/TitleBox", title => loc('Error'), hideable => 0, class => 'error'  &>
-<% $Error %>
-</&>
-% }
+
+<& /Elements/ListActions,
+    title       => loc('Error'),
+    titlebox    => { class => 'error', hideable => 0 },
+    actions     => $actions
+&>
 
 % $m->callback( %ARGS, CallbackName => 'BeforeForm' );
 
 <div id="login-box">
 <&| /Widgets/TitleBox, title => loc('Login'), titleright => $RT::VERSION, hideable => 0 &>
 
 % unless (RT->Config->Get('WebExternalAuth') and !RT->Config->Get('WebFallbackToInternalAuth')) {
-<form id="login" name="login" method="post" action="<% $form_action %>">
+<form id="login" name="login" method="post" action="<% RT->Config->Get('WebPath') %>/NoAuth/Login.html">
 
 <div class="input-row">
     <span class="label"><&|/l&>Username</&>:</span>
     <span class="input"><input name="user" value="<%$user%>" id="user" /></span>
 </div>
@@ -111,44 +76,26 @@ unless (!defined($uri->authority) || $uri->authority eq $uri_base_url->authority
 <div class="input-row">
     <span class="label"><&|/l&>Password</&>:</span>
     <span class="input"><input type="password" name="pass" autocomplete="off" /></span>
 </div>
 
+<input type="hidden" name="next" value="<% $next %>" />
+
 <div class="button-row">
     <span class="input"><input type="submit" class="button" value="<&|/l&>Login</&>" /></span>
 </div>
 
 %# Give callbacks a chance to add more control elements
 % $m->callback( %ARGS );
 
-% # From mason 1.0.1 forward, this doesn't work. in fact, it breaks things.
-% # But on Mason 1.15 it's fixed again, so we still use it.
-% # The code below iterates through everything in the passed in arguments
-% # Preserving all the old parameters
-% # This would be easier, except mason is 'smart' and calls multiple values
-% # arrays rather than multiple hash keys
-% my $key; my $val;
-% foreach $key (keys %ARGS) {
-%  if (($key ne 'user') and ($key ne 'pass')) {
-% 	if (ref($ARGS{$key}) =~ /ARRAY/) {
-% 		foreach $val (@{$ARGS{$key}}) {
-<input type="hidden" class="hidden" name="<%$key %>" value="<% $val %>" />
-% 		}
-% 	}
-%	else {
-<input type="hidden" class="hidden" name="<% $key %>" value="<% $ARGS{$key} %>" />
-% 	}
-%  }
-% }
 </form>
 % }
 </&>
 </div><!-- #login-box -->
 % $m->callback( %ARGS, CallbackName => 'AfterForm' );
 </div><!-- #login-body -->
 <& /Elements/Footer, Menu => 0 &>
 <%ARGS>
+$next => ''
 $user => ""
-$pass => undef
-$goto => undef
-$Error => undef
+$actions => undef
 </%ARGS>
diff --git a/share/html/NoAuth/Login.html b/share/html/NoAuth/Login.html
new file mode 100755
index 000000000..6a3084a00
--- /dev/null
+++ b/share/html/NoAuth/Login.html
@@ -0,0 +1,52 @@
+%# BEGIN BPS TAGGED BLOCK {{{
+%# 
+%# COPYRIGHT:
+%# 
+%# This software is Copyright (c) 1996-2010 Best Practical Solutions, LLC
+%#                                          <jesse@bestpractical.com>
+%# 
+%# (Except where explicitly superseded by other copyright notices)
+%# 
+%# 
+%# LICENSE:
+%# 
+%# This work is made available to you under the terms of Version 2 of
+%# the GNU General Public License. A copy of that license should have
+%# been provided with this software, but in any event can be snarfed
+%# from www.gnu.org.
+%# 
+%# This work is distributed in the hope that it will be useful, but
+%# WITHOUT ANY WARRANTY; without even the implied warranty of
+%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+%# General Public License for more details.
+%# 
+%# You should have received a copy of the GNU General Public License
+%# along with this program; if not, write to the Free Software
+%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+%# 02110-1301 or visit their web page on the internet at
+%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+%# 
+%# 
+%# CONTRIBUTION SUBMISSION POLICY:
+%# 
+%# (The following paragraph is not intended to limit the rights granted
+%# to you to modify and distribute this software under the terms of
+%# the GNU General Public License and is only of importance to you if
+%# you choose to contribute your changes and enhancements to the
+%# community by submitting them to Best Practical Solutions, LLC.)
+%# 
+%# By intentionally submitting any modifications, corrections or
+%# derivatives to this work, or any other work intended for use with
+%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+%# you are the copyright holder for those contributions and you grant
+%# Best Practical Solutions,  LLC a nonexclusive, worldwide, irrevocable,
+%# royalty-free, perpetual, license to use, copy, create derivative
+%# works based on those contributions, and sublicense and distribute
+%# those contributions and any derivatives thereof.
+%# 
+%# END BPS TAGGED BLOCK }}}
+<%init>
+my ($good, $msg) = RT::Interface::Web::AttemptPasswordAuthentication(\%ARGS);
+$ARGS{'actions'} = [$msg] if not $good and $msg;
+</%init>
+<& /Elements/Login, %ARGS &>

Vulnerable code:         # If the user is logging in, let's authenticate
        if ( defined $ARGS->{user} && defined $ARGS->{pass} ) {
            AttemptPasswordAuthentication($ARGS);
        } else {
            # if no credentials then show him login page
            $HTML::Mason::Commands::m->comp( '/Elements/Login', %$ARGS );
            $HTML::Mason::Commands::m->abort;
                $m->comp( '/Elements/Login', %$ARGS, Error => HTML::Mason::Commands::loc( 'Cannot create user: [_1]', $msg ) )
                    if RT->Config->Get('WebFallbackToInternalAuth');;
                $m->abort();
                $m->comp( '/Elements/Login', %$ARGS, Error => HTML::Mason::Commands::loc('You are not an authorized user') );
                $m->abort();
            $m->comp( '/Elements/Login', %$ARGS, Error => HTML::Mason::Commands::loc('You are not an authorized user') );
            $m->abort();
    my $ARGS     = shift;
        $m->comp( '/Elements/Login', %$ARGS, Error => HTML::Mason::Commands::loc('Your username or password is incorrect'), );
        $m->abort;
    $RT::Logger->info("Successful login for @{[$ARGS->{user}]} from $ENV{'REMOTE_ADDR'}");
    InstantiateNewSession();
    $HTML::Mason::Commands::session{'CurrentUser'} = $user_obj;
    $m->callback( %$ARGS, CallbackName => 'SuccessfulLogin', CallbackPage => '/autohandler' );
<&| /Widgets/TitleBox, title => loc('Results') &>
<%INIT>
if ($m->request_comp->path =~ '^/REST/\d+\.\d+/') {
    $r->content_type("text/plain");
    $m->error_format("text");
    $m->out("RT/$RT::VERSION 401 Credentials required\n");
    $m->out("\n$Error\n") if $Error;
    $m->abort;
}

my $req_uri;

if (UNIVERSAL::can($r, 'uri') and $r->uri =~ m{.*/(.*)}) {
    $req_uri = $1;
}

my $form_action = defined $goto             ? $goto
                : defined $req_uri          ? $req_uri
                :                             RT->Config->Get('WebPath')
                ;

# sanitize $form_action
my $uri = URI->new($form_action);

# You get undef scheme with a relative uri like "/Search/Build.html"
unless (!defined($uri->scheme) || $uri->scheme eq 'http' || $uri->scheme eq 'https') {
    $form_action = RT->Config->Get('WebPath');
}

# Make sure we're logging in to the same domain
# You can get an undef authority with a relative uri like "index.html"
my $uri_base_url = URI->new(RT->Config->Get('WebBaseURL'));
unless (!defined($uri->authority) || $uri->authority eq $uri_base_url->authority) {
    $form_action = RT->Config->Get('WebPath');
}
</%INIT>

% if ($Error) {
<&| "/Widgets/TitleBox", title => loc('Error'), hideable => 0, class => 'error'  &>
<% $Error %>
</&>
% }
<form id="login" name="login" method="post" action="<% $form_action %>">
% # From mason 1.0.1 forward, this doesn't work. in fact, it breaks things.
% # But on Mason 1.15 it's fixed again, so we still use it.
% # The code below iterates through everything in the passed in arguments
% # Preserving all the old parameters
% # This would be easier, except mason is 'smart' and calls multiple values
% # arrays rather than multiple hash keys
% my $key; my $val;
% foreach $key (keys %ARGS) {
%  if (($key ne 'user') and ($key ne 'pass')) {
% 	if (ref($ARGS{$key}) =~ /ARRAY/) {
% 		foreach $val (@{$ARGS{$key}}) {
<input type="hidden" class="hidden" name="<%$key %>" value="<% $val %>" />
% 		}
% 	}
%	else {
<input type="hidden" class="hidden" name="<% $key %>" value="<% $ARGS{$key} %>" />
% 	}
%  }
% }
$pass => undef
$goto => undef
$Error => undef
Fixed code:         # Authenticate if the user is trying to login via user/pass query args
        my ($authed, $msg) = AttemptPasswordAuthentication($ARGS);

        unless ($authed) {
            my $m = $HTML::Mason::Commands::m;
            my $results = $msg ? LoginError($msg) : undef;

            # REST urls get a special 401 response
            if ($m->request_comp->path =~ '^/REST/\d+\.\d+/') {
                $HTML::Mason::Commands::r->content_type("text/plain");
                $m->error_format("text");
                $m->out("RT/$RT::VERSION 401 Credentials required\n");
                $m->out("\n$msg\n") if $msg;
                $m->abort;
            }
            # Specially handle /index.html so that we get a nicer URL
            elsif ( $m->request_comp->path eq '/index.html' ) {
                my $next = SetNextPage(RT->Config->Get('WebURL'));
                $m->comp('/NoAuth/Login.html', next => $next, results => $results);
                $m->abort;
            }
            else {
                TangentForLogin(results => $results);
            }
=head2 LoginError ERROR

Pushes a login error into the Actions session store and returns the hash key.

=cut

sub LoginError {
    my $new = shift;
    my $key = Digest::MD5::md5_hex( rand(1024) );
    push @{ $HTML::Mason::Commands::session{"Actions"}->{$key} ||= [] }, $new;
    $HTML::Mason::Commands::session{'i'}++;
    return $key;
}

=head2 SetNextPage [PATH]

Intuits and stashes the next page in the sesssion hash.  If PATH is
specified, uses that instead of the value of L<IntuitNextPage()>.  Returns
the hash value.

=cut

sub SetNextPage {
    my $next = shift || IntuitNextPage();
    my $hash = Digest::MD5::md5_hex( $next . $$ );

    $HTML::Mason::Commands::session{'NextPage'}->{$hash} = $next;
    $HTML::Mason::Commands::session{'i'}++;  # Cargo culted, do we need this?
    
    SendSessionCookie();
    return $hash;
}


=head2 TangentForLogin [HASH]

Redirects to C</NoAuth/Login.html>, setting the value of L<IntuitNextPage> as
the next page.  Optionally takes a hash which is dumped into query params.

=cut

sub TangentForLogin {
    my $hash  = SetNextPage();
    my %query = (@_, next => $hash);
    my $login = RT->Config->Get('WebURL') . 'NoAuth/Login.html?';
    $login .= $HTML::Mason::Commands::m->comp('/Elements/QueryString', %query);
    Redirect($login);
}

=head2 IntuitNextPage

Attempt to figure out the path to which we should return the user after a
tangent.  The current request URL is used, or failing that, the C<WebURL>
configuration variable.

=cut

sub IntuitNextPage {
    my $req_uri;

    # This includes any query parameters.  Redirect will take care of making
    # it an absolute URL.
    $req_uri = $ENV{'REQUEST_URI'} if $ENV{'REQUEST_URI'};

    my $next = defined $req_uri ? $req_uri : RT->Config->Get('WebURL');

    # sanitize $next
    my $uri = URI->new($next);

    # You get undef scheme with a relative uri like "/Search/Build.html"
    unless (!defined($uri->scheme) || $uri->scheme eq 'http' || $uri->scheme eq 'https') {
        $next = RT->Config->Get('WebURL');
    }

    # Make sure we're logging in to the same domain
    # You can get an undef authority with a relative uri like "index.html"
    my $uri_base_url = URI->new(RT->Config->Get('WebBaseURL'));
    unless (!defined($uri->authority) || $uri->authority eq $uri_base_url->authority) {
        $next = RT->Config->Get('WebURL');
    }

    return $next;
}

    # Don't show the login page to logged in users
    Redirect(RT->Config->Get('WebURL'))
        if $m->base_comp->path eq '/NoAuth/Login.html' and _UserLoggedIn();


                if (RT->Config->Get('WebFallbackToInternalAuth')) {
                    my $key = LoginError(
                        HTML::Mason::Commands::loc('Cannot create user: [_1]', $msg)
                    );
                    TangentForLogin( results => $key );
                } else {
                    $m->abort();
                }
                my $key = LoginError(
                    HTML::Mason::Commands::loc('You are not an authorized user')
                );
                TangentForLogin( results => $key );
            my $key = LoginError(
                HTML::Mason::Commands::loc('You are not an authorized user')
            );
            TangentForLogin( results => $key );
    my $ARGS = shift;
    return unless defined $ARGS->{user} && defined $ARGS->{pass};

        return (0, HTML::Mason::Commands::loc('Your username or password is incorrect'));
    else {
        $RT::Logger->info("Successful login for @{[$ARGS->{user}]} from $ENV{'REMOTE_ADDR'}");

        # It's important to nab the next page from the session before we blow
        # the session away
        my $next = $HTML::Mason::Commands::session{'NextPage'}->{$ARGS->{'next'} || ''};
        InstantiateNewSession();
        $HTML::Mason::Commands::session{'CurrentUser'} = $user_obj;
        SendSessionCookie();

        $m->callback( %$ARGS, CallbackName => 'SuccessfulLogin', CallbackPage => '/autohandler' );

        # Really the only time we don't want to redirect here is if we were
        # passed user and pass as query params in the URL.
        if ($next) {
            Redirect($next);
        }
        elsif ($ARGS->{'next'}) {
            # Invalid hash, but still wants to go somewhere, take them to /
            Redirect(RT->Config->Get('WebURL'));
        }

        return (1, HTML::Mason::Commands::loc('Logged in'));
    }
    
    # Make relative URIs absolute from the server host and scheme
    $uri->scheme($server_uri->scheme) if not defined $uri->scheme;
    $uri->host($server_uri->host)     if not defined $uri->host;
<&| /Widgets/TitleBox, title => loc('Results'), %{$titlebox || {}} &>
$titlebox => {}

<& /Elements/ListActions,
    title       => loc('Error'),
    titlebox    => { class => 'error', hideable => 0 },
    actions     => $actions
&>
<form id="login" name="login" method="post" action="<% RT->Config->Get('WebPath') %>/NoAuth/Login.html">
<input type="hidden" name="next" value="<% $next %>" />

$next => ''
$actions => undef
%# BEGIN BPS TAGGED BLOCK {{{
%# 
%# COPYRIGHT:
%# 
%# This software is Copyright (c) 1996-2010 Best Practical Solutions, LLC
%#                                          <jesse@bestpractical.com>
%# 
%# (Except where explicitly superseded by other copyright notices)
%# 
%# 
%# LICENSE:
%# 
%# This work is made available to you under the terms of Version 2 of
%# the GNU General Public License. A copy of that license should have
%# been provided with this software, but in any event can be snarfed
%# from www.gnu.org.
%# 
%# This work is distributed in the hope that it will be useful, but
%# WITHOUT ANY WARRANTY; without even the implied warranty of
%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
%# General Public License for more details.
%# 
%# You should have received a copy of the GNU General Public License
%# along with this program; if not, write to the Free Software
%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
%# 02110-1301 or visit their web page on the internet at
%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
%# 
%# 
%# CONTRIBUTION SUBMISSION POLICY:
%# 
%# (The following paragraph is not intended to limit the rights granted
%# to you to modify and distribute this software under the terms of
%# the GNU General Public License and is only of importance to you if
%# you choose to contribute your changes and enhancements to the
%# community by submitting them to Best Practical Solutions, LLC.)
%# 
%# By intentionally submitting any modifications, corrections or
%# derivatives to this work, or any other work intended for use with
%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
%# you are the copyright holder for those contributions and you grant
%# Best Practical Solutions,  LLC a nonexclusive, worldwide, irrevocable,
%# royalty-free, perpetual, license to use, copy, create derivative
%# works based on those contributions, and sublicense and distribute
%# those contributions and any derivatives thereof.
%# 
%# END BPS TAGGED BLOCK }}}
<%init>
my ($good, $msg) = RT::Interface::Web::AttemptPasswordAuthentication(\%ARGS);
$ARGS{'actions'} = [$msg] if not $good and $msg;
</%init>
<& /Elements/Login, %ARGS &>