Master -> 4.0.5-5
[usit-rt.git] / lib / RT / Interface / Web.pm
index f04d0cc..3cc35b0 100644 (file)
@@ -253,6 +253,7 @@ sub HandleRequest {
     ValidateWebConfig();
 
     DecodeARGS($ARGS);
+    local $HTML::Mason::Commands::DECODED_ARGS = $ARGS;
     PreprocessTimeUpdates($ARGS);
 
     InitializeMenu();
@@ -539,6 +540,10 @@ sub ShowRequestedPage {
 
     my $m = $HTML::Mason::Commands::m;
 
+    # Ensure that the cookie that we send is up-to-date, in case the
+    # session-id has been modified in any way
+    SendSessionCookie();
+
     # precache all system level rights for the current user
     $HTML::Mason::Commands::session{CurrentUser}->PrincipalObj->HasRights( Object => RT->System );
 
@@ -690,7 +695,6 @@ sub AttemptPasswordAuthentication {
 
         InstantiateNewSession();
         $HTML::Mason::Commands::session{'CurrentUser'} = $user_obj;
-        SendSessionCookie();
 
         $m->callback( %$ARGS, CallbackName => 'SuccessfulLogin', CallbackPage => '/autohandler' );
 
@@ -745,6 +749,7 @@ sub LoadSessionFromCookie {
 sub InstantiateNewSession {
     tied(%HTML::Mason::Commands::session)->delete if tied(%HTML::Mason::Commands::session);
     tie %HTML::Mason::Commands::session, 'RT::Interface::Web::Session', undef;
+    SendSessionCookie();
 }
 
 sub SendSessionCookie {
@@ -1161,7 +1166,7 @@ sub ComponentRoots {
     return @roots;
 }
 
-my %is_whitelisted_path = (
+our %is_whitelisted_component = (
     # The RSS feed embeds an auth token in the path, but query
     # information for the search.  Because it's a straight-up read, in
     # addition to embedding its own auth, it's fine.
@@ -1172,7 +1177,7 @@ sub IsCompCSRFWhitelisted {
     my $comp = shift;
     my $ARGS = shift;
 
-    return 1 if $is_whitelisted_path{$comp};
+    return 1 if $is_whitelisted_component{$comp};
 
     my %args = %{ $ARGS };
 
@@ -1200,6 +1205,11 @@ sub IsCompCSRFWhitelisted {
     delete $args{results} if $args{results}
         and $HTML::Mason::Commands::session{"Actions"}->{$args{results}};
 
+    # The homepage refresh, which uses the Refresh header, doesn't send
+    # a referer in most browsers; whitelist the one parameter it reloads
+    # with, HomeRefreshInterval, which is safe
+    delete $args{HomeRefreshInterval};
+
     # If there are no arguments, then it's likely to be an idempotent
     # request, which are not susceptible to CSRF
     return 1 if !%args;
@@ -1209,11 +1219,16 @@ sub IsCompCSRFWhitelisted {
 
 sub IsRefererCSRFWhitelisted {
     my $referer = _NormalizeHost(shift);
-    my $config  = _NormalizeHost(RT->Config->Get('WebBaseURL'));
+    my $base_url = _NormalizeHost(RT->Config->Get('WebBaseURL'));
+    $base_url = $base_url->host_port;
 
-    return (1,$referer,$config) if $referer->host_port eq $config->host_port;
+    my $configs;
+    for my $config ( $base_url, RT->Config->Get('ReferrerWhitelist') ) {
+        push @$configs,$config;
+        return 1 if $referer->host_port eq $config;
+    }
 
-    return (0,$referer,$config);
+    return (0,$referer,$configs);
 }
 
 =head3 _NormalizeHost
@@ -1268,12 +1283,21 @@ EOT
             "your browser did not supply a Referrer header", # loc
         ) if !$ENV{HTTP_REFERER};
 
-    my ($whitelisted, $browser, $config) = IsRefererCSRFWhitelisted($ENV{HTTP_REFERER});
+    my ($whitelisted, $browser, $configs) = IsRefererCSRFWhitelisted($ENV{HTTP_REFERER});
     return 0 if $whitelisted;
 
+    if ( @$configs > 1 ) {
+        return (1,
+                "the Referrer header supplied by your browser ([_1]) is not allowed by RT's configured hostname ([_2]) or whitelisted hosts ([_3])", # loc
+                $browser->host_port,
+                shift @$configs,
+                join(', ', @$configs) );
+    }
+
     return (1,
             "the Referrer header supplied by your browser ([_1]) is not allowed by RT's configured hostname ([_2])", # loc
-            $browser->host_port, $config->host_port);
+            $browser->host_port,
+            $configs->[0]);
 }
 
 sub ExpandCSRFToken {
@@ -1290,6 +1314,7 @@ sub ExpandCSRFToken {
     return unless $user->ValidateAuthString( $data->{auth}, $token );
 
     %{$ARGS} = %{$data->{args}};
+    $HTML::Mason::Commands::DECODED_ARGS = $ARGS;
 
     # We explicitly stored file attachments with the request, but not in
     # the session yet, as that would itself be an attack.  Put them into
@@ -1304,21 +1329,9 @@ sub ExpandCSRFToken {
     return 1;
 }
 
-sub MaybeShowInterstitialCSRFPage {
+sub StoreRequestToken {
     my $ARGS = shift;
 
-    return unless RT->Config->Get('RestrictReferrer');
-
-    # Deal with the form token provided by the interstitial, which lets
-    # browsers which never set referer headers still use RT, if
-    # painfully.  This blows values into ARGS
-    return if ExpandCSRFToken($ARGS);
-
-    my ($is_csrf, $msg, @loc) = IsPossibleCSRF($ARGS);
-    return if !$is_csrf;
-
-    $RT::Logger->notice("Possible CSRF: ".RT::CurrentUser->new->loc($msg, @loc));
-
     my $token = Digest::MD5::md5_hex(time . {} . $$ . rand(1024));
     my $user = $HTML::Mason::Commands::session{'CurrentUser'}->UserObj;
     my $data = {
@@ -1337,10 +1350,28 @@ sub MaybeShowInterstitialCSRFPage {
 
     $HTML::Mason::Commands::session{'CSRF'}->{$token} = $data;
     $HTML::Mason::Commands::session{'i'}++;
+    return $token;
+}
+
+sub MaybeShowInterstitialCSRFPage {
+    my $ARGS = shift;
+
+    return unless RT->Config->Get('RestrictReferrer');
+
+    # Deal with the form token provided by the interstitial, which lets
+    # browsers which never set referer headers still use RT, if
+    # painfully.  This blows values into ARGS
+    return if ExpandCSRFToken($ARGS);
+
+    my ($is_csrf, $msg, @loc) = IsPossibleCSRF($ARGS);
+    return if !$is_csrf;
+
+    $RT::Logger->notice("Possible CSRF: ".RT::CurrentUser->new->loc($msg, @loc));
 
+    my $token = StoreRequestToken($ARGS);
     $HTML::Mason::Commands::m->comp(
         '/Elements/CSRF',
-        OriginalURL => $HTML::Mason::Commands::r->path_info,
+        OriginalURL => RT->Config->Get('WebPath') . $HTML::Mason::Commands::r->path_info,
         Reason => HTML::Mason::Commands::loc( $msg, @loc ),
         Token => $token,
     );