@@ -207,13 +207,29 @@ sub HandleRequest {
207207 unless ( _UserLoggedIn() ) {
208208 _ForceLogout();
209209
210- # If the user is logging in, let's authenticate
211- if ( defined $ARGS -> {user } && defined $ARGS -> {pass } ) {
212- AttemptPasswordAuthentication($ARGS );
213- } else {
214- # if no credentials then show him login page
215- $HTML::Mason::Commands::m -> comp( ' /Elements/Login' , %$ARGS );
216- $HTML::Mason::Commands::m -> abort;
210+ # Authenticate if the user is trying to login via user/pass query args
211+ my ($authed , $msg ) = AttemptPasswordAuthentication($ARGS );
212+
213+ unless ($authed ) {
214+ my $m = $HTML::Mason::Commands::m ;
215+
216+ # REST urls get a special 401 response
217+ if ($m -> request_comp-> path =~ ' ^/REST/\d+\.\d+/' ) {
218+ $HTML::Mason::Commands::r -> content_type(" text/plain" );
219+ $m -> error_format(" text" );
220+ $m -> out(" RT/$RT::VERSION 401 Credentials required\n " );
221+ $m -> out(" \n $msg \n " ) if $msg ;
222+ $m -> abort;
223+ }
224+ # Specially handle /index.html so that we get a nicer URL
225+ elsif ( $m -> request_comp-> path eq ' /index.html' ) {
226+ my $next = SetNextPage(RT-> Config-> Get(' WebURL' ));
227+ $m -> comp(' /NoAuth/Login.html' , next => $next , actions => [$msg ]);
228+ $m -> abort;
229+ }
230+ else {
231+ TangentForLogin(results => ($msg ? LoginError($msg ) : undef ));
232+ }
217233 }
218234 }
219235
@@ -245,6 +261,108 @@ sub _UserLoggedIn {
245261
246262}
247263
264+ =head2 LoginError ERROR
265+
266+ Pushes a login error into the Actions session store and returns the hash key.
267+
268+ =cut
269+
270+ sub LoginError {
271+ my $new = shift ;
272+ my $key = Digest::MD5::md5_hex( rand (1024) );
273+ push @{ $HTML::Mason::Commands::session {" Actions" }-> {$key } ||= [] }, $new ;
274+ $HTML::Mason::Commands::session {' i' }++;
275+ return $key ;
276+ }
277+
278+ =head2 SetNextPage [PATH]
279+
280+ Intuits and stashes the next page in the sesssion hash. If PATH is
281+ specified, uses that instead of the value of L<IntuitNextPage()> . Returns
282+ the hash value.
283+
284+ =cut
285+
286+ sub SetNextPage {
287+ my $next = shift || IntuitNextPage();
288+ my $hash = Digest::MD5::md5_hex($next . $$ . rand (1024));
289+
290+ $HTML::Mason::Commands::session {' NextPage' }-> {$hash } = $next ;
291+ $HTML::Mason::Commands::session {' i' }++;
292+
293+ SendSessionCookie();
294+ return $hash ;
295+ }
296+
297+
298+ =head2 TangentForLogin [HASH]
299+
300+ Redirects to C</NoAuth/Login.html > , setting the value of L<IntuitNextPage> as
301+ the next page. Optionally takes a hash which is dumped into query params.
302+
303+ =cut
304+
305+ sub TangentForLogin {
306+ my $hash = SetNextPage();
307+ my %query = (@_ , next => $hash );
308+ my $login = RT-> Config-> Get(' WebURL' ) . ' NoAuth/Login.html?' ;
309+ $login .= $HTML::Mason::Commands::m -> comp(' /Elements/QueryString' , %query );
310+ Redirect($login );
311+ }
312+
313+ =head2 TangentForLoginWithError ERROR
314+
315+ Localizes the passed error message, stashes it with L<LoginError> and then
316+ calls L<TangentForLogin> with the appropriate results key.
317+
318+ =cut
319+
320+ sub TangentForLoginWithError {
321+ my $key = LoginError(HTML::Mason::Commands::loc(@_ ));
322+ TangentForLogin( results => $key );
323+ }
324+
325+ =head2 IntuitNextPage
326+
327+ Attempt to figure out the path to which we should return the user after a
328+ tangent. The current request URL is used, or failing that, the C<WebURL >
329+ configuration variable.
330+
331+ =cut
332+
333+ sub IntuitNextPage {
334+ my $req_uri ;
335+
336+ # This includes any query parameters. Redirect will take care of making
337+ # it an absolute URL.
338+ if ($ENV {' REQUEST_URI' }) {
339+ $req_uri = $ENV {' REQUEST_URI' };
340+
341+ # collapse multiple leading slashes so the first part doesn't look like
342+ # a hostname of a schema-less URI
343+ $req_uri =~ s { ^/+} { /} ;
344+ }
345+
346+ my $next = defined $req_uri ? $req_uri : RT-> Config-> Get(' WebURL' );
347+
348+ # sanitize $next
349+ my $uri = URI-> new($next );
350+
351+ # You get undef scheme with a relative uri like "/Search/Build.html"
352+ unless (!defined ($uri -> scheme) || $uri -> scheme eq ' http' || $uri -> scheme eq ' https' ) {
353+ $next = RT-> Config-> Get(' WebURL' );
354+ }
355+
356+ # Make sure we're logging in to the same domain
357+ # You can get an undef authority with a relative uri like "index.html"
358+ my $uri_base_url = URI-> new(RT-> Config-> Get(' WebBaseURL' ));
359+ unless (!defined ($uri -> authority) || $uri -> authority eq $uri_base_url -> authority) {
360+ $next = RT-> Config-> Get(' WebURL' );
361+ }
362+
363+ return $next ;
364+ }
365+
248366=head2 MaybeShowInstallModePage
249367
250368This function, called exclusively by RT's autohandler, dispatches
@@ -284,6 +402,10 @@ sub MaybeShowNoAuthPage {
284402
285403 return unless $m -> base_comp-> path =~ RT-> Config-> Get(' WebNoAuthRegex' );
286404
405+ # Don't show the login page to logged in users
406+ Redirect(RT-> Config-> Get(' WebURL' ))
407+ if $m -> base_comp-> path eq ' /NoAuth/Login.html' and _UserLoggedIn();
408+
287409 # If it's a noauth file, don't ask for auth.
288410 SendSessionCookie();
289411 $m -> comp( { base_comp => $m -> request_comp }, $m -> fetch_next, %$ARGS );
@@ -386,9 +508,12 @@ sub AttemptExternalAuth {
386508
387509 # we failed to successfully create the user. abort abort abort.
388510 delete $HTML::Mason::Commands::session {' CurrentUser' };
389- $m -> comp( ' /Elements/Login' , %$ARGS , Error => HTML::Mason::Commands::loc( ' Cannot create user: [_1]' , $msg ) )
390- if RT-> Config-> Get(' WebFallbackToInternalAuth' );;
391- $m -> abort();
511+
512+ if (RT-> Config-> Get(' WebFallbackToInternalAuth' )) {
513+ TangentForLoginWithError(' Cannot create user: [_1]' , $msg );
514+ } else {
515+ $m -> abort();
516+ }
392517 }
393518 }
394519
@@ -399,15 +524,13 @@ sub AttemptExternalAuth {
399524 $user = $orig_user ;
400525
401526 if ( RT-> Config-> Get(' WebExternalOnly' ) ) {
402- $m -> comp( ' /Elements/Login' , %$ARGS , Error => HTML::Mason::Commands::loc(' You are not an authorized user' ) );
403- $m -> abort();
527+ TangentForLoginWithError(' You are not an authorized user' );
404528 }
405529 }
406530 } elsif ( RT-> Config-> Get(' WebFallbackToInternalAuth' ) ) {
407531 unless ( defined $HTML::Mason::Commands::session {' CurrentUser' } ) {
408532 # XXX unreachable due to prior defaulting in HandleRequest (check c34d108)
409- $m -> comp( ' /Elements/Login' , %$ARGS , Error => HTML::Mason::Commands::loc(' You are not an authorized user' ) );
410- $m -> abort();
533+ TangentForLoginWithError(' You are not an authorized user' );
411534 }
412535 } else {
413536
@@ -420,23 +543,44 @@ sub AttemptExternalAuth {
420543}
421544
422545sub AttemptPasswordAuthentication {
423- my $ARGS = shift ;
546+ my $ARGS = shift ;
547+ return unless defined $ARGS -> {user } && defined $ARGS -> {pass };
548+
424549 my $user_obj = RT::CurrentUser-> new();
425550 $user_obj -> Load( $ARGS -> {user } );
426551
427552 my $m = $HTML::Mason::Commands::m ;
428553
429554 unless ( $user_obj -> id && $user_obj -> IsPassword( $ARGS -> {pass } ) ) {
430555 $RT::Logger -> error(" FAILED LOGIN for @{[$ARGS ->{user}]} from $ENV {'REMOTE_ADDR'}" );
431- $m -> comp( ' /Elements/Login' , %$ARGS , Error => HTML::Mason::Commands::loc(' Your username or password is incorrect' ), );
432556 $m -> callback( %$ARGS , CallbackName => ' FailedLogin' , CallbackPage => ' /autohandler' );
433- $m -> abort ;
557+ return (0, HTML::Mason::Commands::loc( ' Your username or password is incorrect ' )) ;
434558 }
559+ else {
560+ $RT::Logger -> info(" Successful login for @{[$ARGS ->{user}]} from $ENV {'REMOTE_ADDR'}" );
561+
562+ # It's important to nab the next page from the session before we blow
563+ # the session away
564+ my $next = delete $HTML::Mason::Commands::session {' NextPage' }-> {$ARGS -> {' next' } || ' ' };
435565
436- $RT::Logger -> info(" Successful login for @{[$ARGS ->{user}]} from $ENV {'REMOTE_ADDR'}" );
437- InstantiateNewSession();
438- $HTML::Mason::Commands::session {' CurrentUser' } = $user_obj ;
439- $m -> callback( %$ARGS , CallbackName => ' SuccessfulLogin' , CallbackPage => ' /autohandler' );
566+ InstantiateNewSession();
567+ $HTML::Mason::Commands::session {' CurrentUser' } = $user_obj ;
568+ SendSessionCookie();
569+
570+ $m -> callback( %$ARGS , CallbackName => ' SuccessfulLogin' , CallbackPage => ' /autohandler' );
571+
572+ # Really the only time we don't want to redirect here is if we were
573+ # passed user and pass as query params in the URL.
574+ if ($next ) {
575+ Redirect($next );
576+ }
577+ elsif ($ARGS -> {' next' }) {
578+ # Invalid hash, but still wants to go somewhere, take them to /
579+ Redirect(RT-> Config-> Get(' WebURL' ));
580+ }
581+
582+ return (1, HTML::Mason::Commands::loc(' Logged in' ));
583+ }
440584}
441585
442586=head2 LoadSessionFromCookie
@@ -503,6 +647,13 @@ sub Redirect {
503647 untie $HTML::Mason::Commands::session ;
504648 my $uri = URI-> new($redir_to );
505649 my $server_uri = URI-> new( RT-> Config-> Get(' WebURL' ) );
650+
651+ # Make relative URIs absolute from the server host and scheme
652+ $uri -> scheme($server_uri -> scheme) if not defined $uri -> scheme;
653+ if (not defined $uri -> host) {
654+ $uri -> host($server_uri -> host);
655+ $uri -> port($server_uri -> port);
656+ }
506657
507658 # If the user is coming in via a non-canonical
508659 # hostname, don't redirect them to the canonical host,
0 commit comments