@@ -207,13 +207,30 @@ 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+ my $results = $msg ? LoginError($msg ) : undef ;
216+
217+ # REST urls get a special 401 response
218+ if ($m -> request_comp-> path =~ ' ^/REST/\d+\.\d+/' ) {
219+ $HTML::Mason::Commands::r -> content_type(" text/plain" );
220+ $m -> error_format(" text" );
221+ $m -> out(" RT/$RT::VERSION 401 Credentials required\n " );
222+ $m -> out(" \n $msg \n " ) if $msg ;
223+ $m -> abort;
224+ }
225+ # Specially handle /index.html so that we get a nicer URL
226+ elsif ( $m -> request_comp-> path eq ' /index.html' ) {
227+ my $next = SetNextPage(RT-> Config-> Get(' WebURL' ));
228+ $m -> comp(' /NoAuth/Login.html' , next => $next , results => $results );
229+ $m -> abort;
230+ }
231+ else {
232+ TangentForLogin(results => $results );
233+ }
217234 }
218235 }
219236
@@ -245,6 +262,90 @@ sub _UserLoggedIn {
245262
246263}
247264
265+ =head2 LoginError ERROR
266+
267+ Pushes a login error into the Actions session store and returns the hash key.
268+
269+ =cut
270+
271+ sub LoginError {
272+ my $new = shift ;
273+ my $key = Digest::MD5::md5_hex( rand (1024) );
274+ push @{ $HTML::Mason::Commands::session {" Actions" }-> {$key } ||= [] }, $new ;
275+ $HTML::Mason::Commands::session {' i' }++;
276+ return $key ;
277+ }
278+
279+ =head2 SetNextPage [PATH]
280+
281+ Intuits and stashes the next page in the sesssion hash. If PATH is
282+ specified, uses that instead of the value of L<IntuitNextPage()> . Returns
283+ the hash value.
284+
285+ =cut
286+
287+ sub SetNextPage {
288+ my $next = shift || IntuitNextPage();
289+ my $hash = Digest::MD5::md5_hex( $next . $$ );
290+
291+ $HTML::Mason::Commands::session {' NextPage' }-> {$hash } = $next ;
292+ $HTML::Mason::Commands::session {' i' }++; # Cargo culted, do we need this?
293+
294+ SendSessionCookie();
295+ return $hash ;
296+ }
297+
298+
299+ =head2 TangentForLogin [HASH]
300+
301+ Redirects to C</NoAuth/Login.html > , setting the value of L<IntuitNextPage> as
302+ the next page. Optionally takes a hash which is dumped into query params.
303+
304+ =cut
305+
306+ sub TangentForLogin {
307+ my $hash = SetNextPage();
308+ my %query = (@_ , next => $hash );
309+ my $login = RT-> Config-> Get(' WebURL' ) . ' NoAuth/Login.html?' ;
310+ $login .= $HTML::Mason::Commands::m -> comp(' /Elements/QueryString' , %query );
311+ Redirect($login );
312+ }
313+
314+ =head2 IntuitNextPage
315+
316+ Attempt to figure out the path to which we should return the user after a
317+ tangent. The current request URL is used, or failing that, the C<WebURL >
318+ configuration variable.
319+
320+ =cut
321+
322+ sub IntuitNextPage {
323+ my $req_uri ;
324+
325+ # This includes any query parameters. Redirect will take care of making
326+ # it an absolute URL.
327+ $req_uri = $ENV {' REQUEST_URI' } if $ENV {' REQUEST_URI' };
328+
329+ my $next = defined $req_uri ? $req_uri : RT-> Config-> Get(' WebURL' );
330+
331+ # sanitize $next
332+ my $uri = URI-> new($next );
333+
334+ # You get undef scheme with a relative uri like "/Search/Build.html"
335+ unless (!defined ($uri -> scheme) || $uri -> scheme eq ' http' || $uri -> scheme eq ' https' ) {
336+ $next = RT-> Config-> Get(' WebURL' );
337+ }
338+
339+ # Make sure we're logging in to the same domain
340+ # You can get an undef authority with a relative uri like "index.html"
341+ my $uri_base_url = URI-> new(RT-> Config-> Get(' WebBaseURL' ));
342+ unless (!defined ($uri -> authority) || $uri -> authority eq $uri_base_url -> authority) {
343+ $next = RT-> Config-> Get(' WebURL' );
344+ }
345+
346+ return $next ;
347+ }
348+
248349=head2 MaybeShowInstallModePage
249350
250351This function, called exclusively by RT's autohandler, dispatches
@@ -284,6 +385,10 @@ sub MaybeShowNoAuthPage {
284385
285386 return unless $m -> base_comp-> path =~ RT-> Config-> Get(' WebNoAuthRegex' );
286387
388+ # Don't show the login page to logged in users
389+ Redirect(RT-> Config-> Get(' WebURL' ))
390+ if $m -> base_comp-> path eq ' /NoAuth/Login.html' and _UserLoggedIn();
391+
287392 # If it's a noauth file, don't ask for auth.
288393 SendSessionCookie();
289394 $m -> comp( { base_comp => $m -> request_comp }, $m -> fetch_next, %$ARGS );
@@ -386,9 +491,15 @@ sub AttemptExternalAuth {
386491
387492 # we failed to successfully create the user. abort abort abort.
388493 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();
494+
495+ if (RT-> Config-> Get(' WebFallbackToInternalAuth' )) {
496+ my $key = LoginError(
497+ HTML::Mason::Commands::loc(' Cannot create user: [_1]' , $msg )
498+ );
499+ TangentForLogin( results => $key );
500+ } else {
501+ $m -> abort();
502+ }
392503 }
393504 }
394505
@@ -399,15 +510,19 @@ sub AttemptExternalAuth {
399510 $user = $orig_user ;
400511
401512 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();
513+ my $key = LoginError(
514+ HTML::Mason::Commands::loc(' You are not an authorized user' )
515+ );
516+ TangentForLogin( results => $key );
404517 }
405518 }
406519 } elsif ( RT-> Config-> Get(' WebFallbackToInternalAuth' ) ) {
407520 unless ( defined $HTML::Mason::Commands::session {' CurrentUser' } ) {
408521 # 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();
522+ my $key = LoginError(
523+ HTML::Mason::Commands::loc(' You are not an authorized user' )
524+ );
525+ TangentForLogin( results => $key );
411526 }
412527 } else {
413528
@@ -420,23 +535,44 @@ sub AttemptExternalAuth {
420535}
421536
422537sub AttemptPasswordAuthentication {
423- my $ARGS = shift ;
538+ my $ARGS = shift ;
539+ return unless defined $ARGS -> {user } && defined $ARGS -> {pass };
540+
424541 my $user_obj = RT::CurrentUser-> new();
425542 $user_obj -> Load( $ARGS -> {user } );
426543
427544 my $m = $HTML::Mason::Commands::m ;
428545
429546 unless ( $user_obj -> id && $user_obj -> IsPassword( $ARGS -> {pass } ) ) {
430547 $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' ), );
432548 $m -> callback( %$ARGS , CallbackName => ' FailedLogin' , CallbackPage => ' /autohandler' );
433- $m -> abort ;
549+ return (0, HTML::Mason::Commands::loc( ' Your username or password is incorrect ' )) ;
434550 }
551+ else {
552+ $RT::Logger -> info(" Successful login for @{[$ARGS ->{user}]} from $ENV {'REMOTE_ADDR'}" );
553+
554+ # It's important to nab the next page from the session before we blow
555+ # the session away
556+ my $next = $HTML::Mason::Commands::session {' NextPage' }-> {$ARGS -> {' next' } || ' ' };
435557
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' );
558+ InstantiateNewSession();
559+ $HTML::Mason::Commands::session {' CurrentUser' } = $user_obj ;
560+ SendSessionCookie();
561+
562+ $m -> callback( %$ARGS , CallbackName => ' SuccessfulLogin' , CallbackPage => ' /autohandler' );
563+
564+ # Really the only time we don't want to redirect here is if we were
565+ # passed user and pass as query params in the URL.
566+ if ($next ) {
567+ Redirect($next );
568+ }
569+ elsif ($ARGS -> {' next' }) {
570+ # Invalid hash, but still wants to go somewhere, take them to /
571+ Redirect(RT-> Config-> Get(' WebURL' ));
572+ }
573+
574+ return (1, HTML::Mason::Commands::loc(' Logged in' ));
575+ }
440576}
441577
442578=head2 LoadSessionFromCookie
@@ -503,6 +639,10 @@ sub Redirect {
503639 untie $HTML::Mason::Commands::session ;
504640 my $uri = URI-> new($redir_to );
505641 my $server_uri = URI-> new( RT-> Config-> Get(' WebURL' ) );
642+
643+ # Make relative URIs absolute from the server host and scheme
644+ $uri -> scheme($server_uri -> scheme) if not defined $uri -> scheme;
645+ $uri -> host($server_uri -> host) if not defined $uri -> host;
506646
507647 # If the user is coming in via a non-canonical
508648 # hostname, don't redirect them to the canonical host,
0 commit comments