[Rt-commit] rt branch, 4.0-trunk, updated. rt-4.0.12-61-gb8105f1

Thomas Sibley trs at bestpractical.com
Wed May 22 14:36:32 EDT 2013


The branch, 4.0-trunk has been updated
       via  b8105f1750064fd5d52be2a4e4810bd471c7e443 (commit)
       via  90b6e7cb80c686b6bf41067029e75914748a4525 (commit)
       via  f4dec8a834ec8a21221ab48ce13bbf56f80248b1 (commit)
       via  5ff6f8d0f2a1569e10e74bfa71aac8ec8849cf5a (commit)
       via  42ee5256ef9e8402b3f84b438da6ec61e70eb728 (commit)
       via  263e69cb71714125a1f6424899f882d4ef6bf058 (commit)
       via  03d71ac3ae9581ab3c416ad656f4312775e81a00 (commit)
       via  51ab4ef85f4cabe2838bd078a138031ba690fd9c (commit)
       via  a9f5f6aa1390279b41996c3c404223288bfc3e0e (commit)
       via  082cb7537dd96b16e7b37d68d798d4e971c11fa6 (commit)
       via  fb2c9c07ebc86f5c7fef82d9704e7a6f3d7d69dc (commit)
       via  5dd6965cbb5e85e149ea22045627dcf6fb77fbe9 (commit)
       via  f19e3ea836a9e4612ae7256610d52014857a8c96 (commit)
       via  3af6cb052941d0fd6fab1f6bc28feaad85a1f8ee (commit)
       via  15857be24af0955e5eeb46e6bab96378704e1e09 (commit)
       via  6eac780d28eb94eab22ccfd8e0dc5170ff450231 (commit)
       via  798505e632322514ead81eee0d0b8fb32999d202 (commit)
       via  e59d4076dfc00f4c1ce271b17bee210ea9f17de1 (commit)
       via  8a9efd5d727936e6065f6060569f71f3cc8b564a (commit)
       via  647a06d551933590a248331218ddfd96d24a3b32 (commit)
       via  00b7c820fb8faec20d46f808706e5a12cd8d6d18 (commit)
       via  9c5e5a7f8dc36f49c0b91a3094afe8cdfc4d0778 (commit)
       via  504d7bb6ad270ad8dc5a2f712b7cf8bd0a5934c7 (commit)
       via  6d0b9ae59787e0211451b48d02934569bbfc3b34 (commit)
       via  932e41b1f247c88f4cf793f68cc63182c10508bb (commit)
       via  15c2568d0797966c96e7541869b8e90549c874f2 (commit)
       via  ea85bb1c151507886a274bd5d869a0cd2d489b4c (commit)
      from  1d1ff68e5ebb7b4d066ce48c95e90c40724ad1d7 (commit)

Summary of changes:
 bin/rt.in                                          |  10 +-
 etc/upgrade/4.0.13/schema.Oracle                   |   2 +
 etc/upgrade/4.0.13/schema.Pg                       |   2 +
 etc/upgrade/4.0.13/schema.mysql                    |   2 +
 lib/RT/Interface/Web.pm                            |   9 +-
 lib/RT/Lifecycle.pm                                | 140 +++++++++++++++++----
 lib/RT/Ticket.pm                                   |  23 +++-
 lib/RT/Tickets.pm                                  |   2 +
 lib/RT/Transaction.pm                              |   7 +-
 .../Tools/Shredder/Elements/Object/RT--Attachment  |   2 +-
 share/html/Download/CustomFieldValue/dhandler      |   2 +-
 share/html/Elements/ColumnMap                      |   6 +-
 share/html/Elements/EditCustomFieldBinary          |   2 +-
 share/html/Elements/MakeClicky                     |  19 +--
 share/html/Elements/ShowCustomFieldBinary          |   2 +-
 share/html/NoAuth/Logout.html                      |   2 +-
 share/html/REST/1.0/logout                         |   5 +-
 share/html/Ticket/Attachment/dhandler              |   2 +-
 share/html/Ticket/Elements/ShowAttachments         |   2 +-
 .../Ticket/Elements/ShowTransactionAttachments     |   5 +-
 share/html/m/logout                                |   2 +-
 share/html/m/ticket/show                           |   2 +-
 22 files changed, 184 insertions(+), 66 deletions(-)
 create mode 100644 etc/upgrade/4.0.13/schema.Oracle
 create mode 100644 etc/upgrade/4.0.13/schema.Pg
 create mode 100644 etc/upgrade/4.0.13/schema.mysql

- Log -----------------------------------------------------------------
commit ea85bb1c151507886a274bd5d869a0cd2d489b4c
Author: Thomas Sibley <trs at bestpractical.com>
Date:   Thu Feb 21 15:56:19 2013 -0800

    Instantiate new sessions on logout as well as deleting the old one
    
    Although the stored session is deleted by tied(%session)->delete, the
    tied object and all data still remains present in memory.  When %session
    goes out of scope at the end of the request, the Apache::Session DESTROY
    block attempts to save the session one last time.  On database session
    stores (MySQL and Pg), the update silently fails because the row has
    been deleted (and no error checking is done).  When using
    Apache::Session::File, however, session updates autovivify the file,
    essentially reverting the deletion.  The autovivified session contained
    an empty current user, since the CU is replaced after the delete, but
    all other user-specific session data still existed and was in the
    session of the unauthenticated client.  This lead to incorrect behaviour
    in extensions outside of core RT.
    
    The old session id in the client's cookie is now immediately replaced
    with a new session id.  Previously it was replaced it on the very next
    request (meta-refresh to the login page) when the old id could not be
    loaded.  Under Apache::Session::File, the old session was not replaced
    at all until a successful login.

diff --git a/share/html/NoAuth/Logout.html b/share/html/NoAuth/Logout.html
index 1d933dd..7c9e0cb 100644
--- a/share/html/NoAuth/Logout.html
+++ b/share/html/NoAuth/Logout.html
@@ -76,7 +76,7 @@ $m->callback( %ARGS, URL => \$URL );
 $m->callback( %ARGS, CallbackName => 'BeforeSessionDelete' );
 
 if (keys %session) {
-    tied(%session)->delete;
+    RT::Interface::Web::InstantiateNewSession();
     $session{'CurrentUser'} = RT::CurrentUser->new;
 }
 
diff --git a/share/html/REST/1.0/logout b/share/html/REST/1.0/logout
index 1ef4319..4ce4d82 100644
--- a/share/html/REST/1.0/logout
+++ b/share/html/REST/1.0/logout
@@ -46,6 +46,9 @@
 %#
 %# END BPS TAGGED BLOCK }}}
 <%PERL>
-tied(%session)->delete if keys %session;
+if (keys %session) {
+    RT::Interface::Web::InstantiateNewSession();
+    $session{CurrentUser} = RT::CurrentUser->new();
+}
 </%PERL>
 RT/<% $RT::VERSION %> 200 Ok
diff --git a/share/html/m/logout b/share/html/m/logout
index b1f8766..f1127a3 100644
--- a/share/html/m/logout
+++ b/share/html/m/logout
@@ -47,7 +47,7 @@
 %# END BPS TAGGED BLOCK }}}
 <%init>
 if (keys %session) {
-    tied(%session)->delete;
+    RT::Interface::Web::InstantiateNewSession();
     $session{'CurrentUser'} = RT::CurrentUser->new;
 }
 RT::Interface::Web::Redirect(RT->Config->Get('WebURL')."m/");

commit 15c2568d0797966c96e7541869b8e90549c874f2
Author: Thomas Sibley <trs at bestpractical.com>
Date:   Thu Feb 21 16:34:59 2013 -0800

    Instantiate a new session if the session doesn't match the ID we loaded it by
    
    The replaced code was a no-op since the refactor in 64f65d8.  Before the
    64f65d8, deleting from the parsed cookie data was enough to set a new
    cookie.  The new cookie contained the found session id, regardless of if
    it matched the loaded session id.
    
    A safer, more conservative approach is to abandon the old session and
    force a new one if the session cookie value loads a session with a
    different id.  This should never happen but indicates corrupt session
    data or malicious activity if it does.  There is no harm in the extra
    protection.

diff --git a/lib/RT/Interface/Web.pm b/lib/RT/Interface/Web.pm
index 7442272..d02aee7 100644
--- a/lib/RT/Interface/Web.pm
+++ b/lib/RT/Interface/Web.pm
@@ -799,7 +799,7 @@ sub LoadSessionFromCookie {
     my $SessionCookie = ( $cookies{$cookiename} ? $cookies{$cookiename}->value : undef );
     tie %HTML::Mason::Commands::session, 'RT::Interface::Web::Session', $SessionCookie;
     unless ( $SessionCookie && $HTML::Mason::Commands::session{'_session_id'} eq $SessionCookie ) {
-        undef $cookies{$cookiename};
+        InstantiateNewSession();
     }
     if ( int RT->Config->Get('AutoLogoff') ) {
         my $now = int( time / 60 );

commit 932e41b1f247c88f4cf793f68cc63182c10508bb
Author: Alex Vandiver <alexmv at bestpractical.com>
Date:   Wed Apr 17 20:25:22 2013 -0400

    Ensure that filenames in inline image attributes are HTML-escaped

diff --git a/share/html/Ticket/Elements/ShowTransactionAttachments b/share/html/Ticket/Elements/ShowTransactionAttachments
index 9ff6d10..bf9aad0 100644
--- a/share/html/Ticket/Elements/ShowTransactionAttachments
+++ b/share/html/Ticket/Elements/ShowTransactionAttachments
@@ -257,12 +257,13 @@ my $render_attachment = sub {
         }
 
         my $filename =  length $name ? $name : loc('(untitled)');
+        my $efilename = $m->interp->apply_escapes( $filename, 'h' );
         $m->out('<img'
               . ' alt="'
-              . $filename
+              . $efilename
               . '"' 
               . ' title="'
-              . $filename
+              . $efilename
               . '"' 
               . ' src="'
               . $AttachPath . '/'

commit 6d0b9ae59787e0211451b48d02934569bbfc3b34
Author: Alex Vandiver <alexmv at bestpractical.com>
Date:   Thu Apr 18 16:20:43 2013 -0400

    Deny direct access to callbacks
    
    Callbacks are meant to be called from the context of an core Mason
    component, and do not expect to receive arbitrary input from query
    parameters.  As such, deny access to them from the top-level request.

diff --git a/lib/RT/Interface/Web.pm b/lib/RT/Interface/Web.pm
index ba7b56d..006c672 100644
--- a/lib/RT/Interface/Web.pm
+++ b/lib/RT/Interface/Web.pm
@@ -574,6 +574,7 @@ sub MaybeRejectPrivateComponentRequest {
             / # leading slash
             ( Elements    |
               _elements   | # mobile UI
+              Callbacks   |
               Widgets     |
               autohandler | # requesting this directly is suspicious
               l (_unsafe)? ) # loc component

commit 504d7bb6ad270ad8dc5a2f712b7cf8bd0a5934c7
Author: Alex Vandiver <alexmv at bestpractical.com>
Date:   Thu Apr 18 19:30:25 2013 -0400

    Protect calls to $m->comp with user input in ColumnMap
    
    $Class may leak in from the user, which would allow calling arbitrary
    components.  Protect it by using ComponentPathIsSafe, extended to ensure
    that the path cannot be truncated early by way of embedded nulls.

diff --git a/lib/RT/Interface/Web.pm b/lib/RT/Interface/Web.pm
index ba7b56d..c0e5750 100644
--- a/lib/RT/Interface/Web.pm
+++ b/lib/RT/Interface/Web.pm
@@ -947,15 +947,15 @@ sub StaticFileHeaders {
 Takes C<PATH> and returns a boolean indicating that the user-specified partial
 component path is safe.
 
-Currently "safe" means that the path does not start with a dot (C<.>) and does
-not contain a slash-dot C</.>.
+Currently "safe" means that the path does not start with a dot (C<.>), does
+not contain a slash-dot C</.>, and does not contain any nulls.
 
 =cut
 
 sub ComponentPathIsSafe {
     my $self = shift;
     my $path = shift;
-    return $path !~ m{(?:^|/)\.};
+    return $path !~ m{(?:^|/)\.} and $path !~ m{\0};
 }
 
 =head2 PathIsSafe
diff --git a/share/html/Elements/ColumnMap b/share/html/Elements/ColumnMap
index ac65459..330aced 100644
--- a/share/html/Elements/ColumnMap
+++ b/share/html/Elements/ColumnMap
@@ -182,8 +182,10 @@ $m->callback( COLUMN_MAP => $COLUMN_MAP, CallbackName => 'Once', CallbackOnce =>
 $m->callback( COLUMN_MAP => $COLUMN_MAP );
 
 # first deal with class specific things
-my $class_map = $m->comp("/Elements/$Class/ColumnMap", Attr => $Attr, Name => $Name );
-return $class_map if defined $class_map;
+if (RT::Interface::Web->ComponentPathIsSafe($Class) and $m->comp_exists("/Elements/$Class/ColumnMap")) {
+    my $class_map = $m->comp("/Elements/$Class/ColumnMap", Attr => $Attr, Name => $Name );
+    return $class_map if defined $class_map;
+}
 return GetColumnMapEntry( Map => $COLUMN_MAP, Name => $Name, Attribute => $Attr );
 
 </%INIT>

commit 9c5e5a7f8dc36f49c0b91a3094afe8cdfc4d0778
Author: Alex Vandiver <alexmv at bestpractical.com>
Date:   Fri Apr 19 16:08:36 2013 -0400

    Remove filename= suggesions from Content-Disposition lines
    
    No supported standard exists for escaping of the "filename" attribute of
    Content-Disposition.  This opens the possibility of various injection
    attacks through carefully-crafted filenames if AlwaysDownloadAttachments
    is enabled.
    
    As the download endpoint is a dhandler, and most locations already
    supply the filename after the final /, browsers will assume the correct
    filename even if none is supplied to Content-Disposition.  Hence, remove
    the filename= parameter entirely.

diff --git a/share/html/Download/CustomFieldValue/dhandler b/share/html/Download/CustomFieldValue/dhandler
index ced11fb..a919049 100644
--- a/share/html/Download/CustomFieldValue/dhandler
+++ b/share/html/Download/CustomFieldValue/dhandler
@@ -64,7 +64,7 @@ unless ($OCFV->id) {
 my $content_type = $OCFV->ContentType || 'text/plain';
     
 if (RT->Config->Get('AlwaysDownloadAttachments')) {
-    $r->headers_out->{'Content-Disposition'} = "attachment; filename=" . $OCFV->Content;
+    $r->headers_out->{'Content-Disposition'} = "attachment";
 }
 elsif (!RT->Config->Get('TrustHTMLAttachments')) {
     $content_type = 'text/plain' if ($content_type =~ /^text\/html/i);
diff --git a/share/html/Ticket/Attachment/dhandler b/share/html/Ticket/Attachment/dhandler
index ae9580a..f30232e 100644
--- a/share/html/Ticket/Attachment/dhandler
+++ b/share/html/Ticket/Attachment/dhandler
@@ -70,7 +70,7 @@
      my $content_type = $AttachmentObj->ContentType || 'text/plain';
 
      if (RT->Config->Get('AlwaysDownloadAttachments')) {
-         $r->headers_out->{'Content-Disposition'} = "attachment; filename=" . $AttachmentObj->Filename;
+         $r->headers_out->{'Content-Disposition'} = "attachment";
      }
      elsif (!RT->Config->Get('TrustHTMLAttachments')) {
          $content_type = 'text/plain' if ($content_type =~ /^text\/html/i);

commit 00b7c820fb8faec20d46f808706e5a12cd8d6d18
Author: Alex Vandiver <alexmv at bestpractical.com>
Date:   Fri Apr 19 16:48:49 2013 -0400

    Ensure consistent escaping of filenames in attachment URIs
    
    <% $value |u %> still applies HTML escaping before URI escaping, which
    is incorrect, as it leads to files named «a"test".txt» being linked with
    URIs like «a%26%2334%3Btest%26%2334%3B.txt» -- the URI-escaped version
    of «a"test".txt».
    
    Ensure that these URIs are only URI-escaped (yilding «a%22test%22.txt»
    in this example), such that browsers correctly parse the intended
    filename from the URI.

diff --git a/share/html/Admin/Tools/Shredder/Elements/Object/RT--Attachment b/share/html/Admin/Tools/Shredder/Elements/Object/RT--Attachment
index 864290c..cc6dd7d 100644
--- a/share/html/Admin/Tools/Shredder/Elements/Object/RT--Attachment
+++ b/share/html/Admin/Tools/Shredder/Elements/Object/RT--Attachment
@@ -49,6 +49,6 @@
 $Object => undef
 </%ARGS>
 % my $name = (defined $Object->Filename and length $Object->Filename) ? $Object->Filename : loc("(no value)");
-<a href="<% RT->Config->Get('WebPath') %>/Ticket/Attachment/<% $Object->TransactionId %>/<% $Object->id %>/">
+<a href="<% RT->Config->Get('WebPath') %>/Ticket/Attachment/<% $Object->TransactionId %>/<% $Object->id %>/<% $Object->Filename |un %>">
 <% loc('Attachment') %>(<% loc('id') %>:<% $Object->id %>, <% loc('Filename') %>: <% $name %>)
 </a>
diff --git a/share/html/Elements/EditCustomFieldBinary b/share/html/Elements/EditCustomFieldBinary
index 4700b5d..c74bfd0 100644
--- a/share/html/Elements/EditCustomFieldBinary
+++ b/share/html/Elements/EditCustomFieldBinary
@@ -47,7 +47,7 @@
 %# END BPS TAGGED BLOCK }}}
 % while ( $Values and my $value = $Values->Next ) {
 %# XXX - let user download the file(s) here?
-<input type="checkbox" class="checkbox" name="<%$NamePrefix%><%$CustomField->Id%>-DeleteValueIds" class="CF-<%$CustomField->id%>-Edit" value="<% $value->Id %>" /><a href="<%RT->Config->Get('WebPath')%>/Download/CustomFieldValue/<% $value->Id %>/<% $value->Content %>"><% $value->Content %></a><br />
+<input type="checkbox" class="checkbox" name="<%$NamePrefix%><%$CustomField->Id%>-DeleteValueIds" class="CF-<%$CustomField->id%>-Edit" value="<% $value->Id %>" /><a href="<%RT->Config->Get('WebPath')%>/Download/CustomFieldValue/<% $value->Id %>/<% $value->Content |un %>"><% $value->Content %></a><br />
 % }
 % if (!$MaxValues || !$Values || $Values->Count < $MaxValues) {
 <input type="file" name="<% $NamePrefix %><% $CustomField->Id %>-Upload" class="CF-<%$CustomField->id%>-Edit" />
diff --git a/share/html/Elements/ShowCustomFieldBinary b/share/html/Elements/ShowCustomFieldBinary
index 99de40a..306f463 100644
--- a/share/html/Elements/ShowCustomFieldBinary
+++ b/share/html/Elements/ShowCustomFieldBinary
@@ -45,7 +45,7 @@
 %# those contributions and any derivatives thereof.
 %#
 %# END BPS TAGGED BLOCK }}}
-<a href="<%RT->Config->Get('WebPath')%>/Download/CustomFieldValue/<% $Object->Id %>/<% $Object->Content |u %>"><% $Object->Content %></a>
+<a href="<%RT->Config->Get('WebPath')%>/Download/CustomFieldValue/<% $Object->Id %>/<% $Object->Content |un %>"><% $Object->Content %></a>
 <%ARGS>
 $Object => undef
 </%ARGS>
diff --git a/share/html/Ticket/Elements/ShowAttachments b/share/html/Ticket/Elements/ShowAttachments
index 2b435bf..c487fee 100644
--- a/share/html/Ticket/Elements/ShowAttachments
+++ b/share/html/Ticket/Elements/ShowAttachments
@@ -82,7 +82,7 @@ if ($size) {
 </%PERL>
 
 <li><font size="-2">
-<a href="<%RT->Config->Get('WebPath')%>/Ticket/Attachment/<%$rev->TransactionId%>/<%$rev->Id%>/<%$rev->Filename | u%>">
+<a href="<%RT->Config->Get('WebPath')%>/Ticket/Attachment/<%$rev->TransactionId%>/<%$rev->Id%>/<%$rev->Filename | un %>">
 % my $desc = loc("[_1] ([_2]) by [_3]", $rev->CreatedAsString, $size, $m->scomp('/Elements/ShowUser', User => $rev->CreatorObj));
 <% $desc |n%>
 </a>
diff --git a/share/html/m/ticket/show b/share/html/m/ticket/show
index df16eb2..ead23a7 100644
--- a/share/html/m/ticket/show
+++ b/share/html/m/ticket/show
@@ -344,7 +344,7 @@ if ($size) {
 </%PERL>
 
 <li><font size="-2">
-<a href="<%RT->Config->Get('WebPath')%>/Ticket/Attachment/<%$rev->TransactionId%>/<%$rev->Id%>/<%$rev->Filename | u%>">
+<a href="<%RT->Config->Get('WebPath')%>/Ticket/Attachment/<%$rev->TransactionId%>/<%$rev->Id%>/<%$rev->Filename | un%>">
 <&|/l, $rev->CreatedAsString, $size, $rev->CreatorObj->Name &>[_1] ([_2]) by [_3]</&>
 </a>
 </font></li>

commit 647a06d551933590a248331218ddfd96d24a3b32
Author: Alex Vandiver <alexmv at bestpractical.com>
Date:   Wed Apr 17 20:04:14 2013 -0400

    Ensure that URLs placed in HTML attributes are escaped correctly, to prevent XSS injection

diff --git a/share/html/Elements/MakeClicky b/share/html/Elements/MakeClicky
index e989edc..abe9b27 100644
--- a/share/html/Elements/MakeClicky
+++ b/share/html/Elements/MakeClicky
@@ -48,6 +48,12 @@
 <%ONCE>
 use Regexp::Common qw(URI);
 
+my $escaper = sub {
+    my $content = shift;
+    RT::Interface::Web::EscapeUTF8( \$content );
+    return $content;
+};
+
 my %actions = (
     default => sub {
         my %args = @_;
@@ -55,13 +61,14 @@ my %actions = (
     },
     url => sub {
         my %args = @_;
+        $args{value} = $escaper->($args{value});
         my $result = qq{[<a target="new" href="$args{value}">}. loc('Open URL') .qq{</a>]};
         return $args{value} . qq{ <span class="clickylink">$result</span>};
     },
     url_overwrite => sub {
         my %args = @_;
-        my $result = qq{<a target="new" href="$args{'value'}">};
-        $result .= qq{$args{'value'}</a>};
+        $args{value} = $escaper->($args{value});
+        my $result = qq{<a target="new" href="$args{value}">$args{value}</a>};
         return qq{<span class="clickylink">$result</span>};
     },
 );
@@ -89,12 +96,6 @@ my $handle = sub {
     }
 };
 
-my $escaper = sub {
-    my $content = shift;
-    RT::Interface::Web::EscapeUTF8( \$content );
-    return $content;
-};
-
 # Hook to add more Clicky types
 # XXX Have to have Page argument, as Mason gets caller wrong in Callback?
 # This happens as we are in <%ONCE> block

commit 8a9efd5d727936e6065f6060569f71f3cc8b564a
Author: Alex Vandiver <alexmv at bestpractical.com>
Date:   Wed Apr 24 23:51:59 2013 -0400

    Ensure that the default replacement does not pass through unescaped content
    
    This is only relevant in the case of a MakeClicky misconfiguration.

diff --git a/share/html/Elements/MakeClicky b/share/html/Elements/MakeClicky
index abe9b27..dabe3bb 100644
--- a/share/html/Elements/MakeClicky
+++ b/share/html/Elements/MakeClicky
@@ -57,7 +57,7 @@ my $escaper = sub {
 my %actions = (
     default => sub {
         my %args = @_;
-        return $args{value};
+        return $escaper->($args{value});
     },
     url => sub {
         my %args = @_;

commit e59d4076dfc00f4c1ce271b17bee210ea9f17de1
Author: Alex Vandiver <alexmv at bestpractical.com>
Date:   Thu Apr 18 17:59:09 2013 -0400

    Use File::Temp for non-predictable temporary filenames
    
    Previously, RT wrote to /tmp/rt.form.$$, which is a semi-predictable
    filename.  A malicious user could symlink a file of their choosing into
    that location, which a privileged user running bin/rt would then
    overwrite.  While this race condition would be difficult to exploit, as
    it leaves the attacker with no control over the contents of the file,
    and relies on beating the race condition, it is still potentially
    dangerous.
    
    Use File::Temp to atomically generate a non-predictable filename and
    open it for writing.

diff --git a/bin/rt.in b/bin/rt.in
index 64975ad..4a3eada 100644
--- a/bin/rt.in
+++ b/bin/rt.in
@@ -68,6 +68,7 @@ use HTTP::Request::Common;
 use HTTP::Headers;
 use Term::ReadLine;
 use Time::Local; # used in prettyshow
+use File::Temp;
 
 # strong (GSSAPI based) authentication is supported if the server does provide
 # it and the perl modules GSSAPI and LWP::Authen::Negotiate are installed
@@ -1467,23 +1468,20 @@ sub read_passwd {
 
 sub vi {
     my ($text) = @_;
-    my $file = "/tmp/rt.form.$$";
     my $editor = $ENV{EDITOR} || $ENV{VISUAL} || "vi";
 
     local $/ = undef;
 
-    open( my $handle, '>', $file ) or die "$file: $!\n";
+    my $handle = File::Temp->new;
     print $handle $text;
     close($handle);
 
-    system($editor, $file) && die "Couldn't run $editor.\n";
+    system($editor, $handle->filename) && die "Couldn't run $editor.\n";
 
-    open( $handle, '<', $file ) or die "$file: $!\n";
+    open( $handle, '<', $handle->filename ) or die "$handle: $!\n";
     $text = <$handle>;
     close($handle);
 
-    unlink($file);
-
     return $text;
 }
 

commit 798505e632322514ead81eee0d0b8fb32999d202
Author: Alex Vandiver <alexmv at bestpractical.com>
Date:   Wed Apr 17 17:45:44 2013 -0400

    Canonicalize on lower-case for statuses, which are now case-insensitive
    
    Prior to the merge of lifecycles (in 2ad74ba), statuses were fully
    case-sensitive.  This meant that attempts to set the status to 'OPEN'
    would fail, as the case did not match the supported 'open' status.
    Lifecycles made checks for _validation_ of statuses case-insensitive,
    but did not spread that property to many other parts of the system --
    including rights, transitions, and searching.
    
    Canonicalize all statuses to lower-case in the database, and make the
    remaining entry points into the API lower-case as well.  This also
    requires checking that all statuses described in lifecycles are
    correctly in lower-case.  While using perl 5.16's "fc" operator would be
    more correct in this case, this is not possible on all versions of Perl
    that RT currently runs on.

diff --git a/etc/upgrade/4.0.13/schema.Oracle b/etc/upgrade/4.0.13/schema.Oracle
new file mode 100644
index 0000000..e43e40f
--- /dev/null
+++ b/etc/upgrade/4.0.13/schema.Oracle
@@ -0,0 +1,2 @@
+UPDATE Tickets SET Status = LOWER(Status);
+UPDATE Transactions SET OldValue = LOWER(OldValue), NewValue = LOWER(NewValue) WHERE Type = 'Status' AND Field = 'Status';
diff --git a/etc/upgrade/4.0.13/schema.Pg b/etc/upgrade/4.0.13/schema.Pg
new file mode 100644
index 0000000..e43e40f
--- /dev/null
+++ b/etc/upgrade/4.0.13/schema.Pg
@@ -0,0 +1,2 @@
+UPDATE Tickets SET Status = LOWER(Status);
+UPDATE Transactions SET OldValue = LOWER(OldValue), NewValue = LOWER(NewValue) WHERE Type = 'Status' AND Field = 'Status';
diff --git a/etc/upgrade/4.0.13/schema.mysql b/etc/upgrade/4.0.13/schema.mysql
new file mode 100644
index 0000000..e43e40f
--- /dev/null
+++ b/etc/upgrade/4.0.13/schema.mysql
@@ -0,0 +1,2 @@
+UPDATE Tickets SET Status = LOWER(Status);
+UPDATE Transactions SET OldValue = LOWER(OldValue), NewValue = LOWER(NewValue) WHERE Type = 'Status' AND Field = 'Status';
diff --git a/lib/RT/Lifecycle.pm b/lib/RT/Lifecycle.pm
index c819559..109b784 100644
--- a/lib/RT/Lifecycle.pm
+++ b/lib/RT/Lifecycle.pm
@@ -411,8 +411,8 @@ sub Transitions {
     return %{ $self->{'data'}{'transitions'} || {} }
         unless @_;
 
-    my $status = shift;
-    return @{ $self->{'data'}{'transitions'}{ $status || '' } || [] };
+    my $status = shift || '';
+    return @{ $self->{'data'}{'transitions'}{ lc $status } || [] };
 }
 
 =head1 IsTransition
@@ -439,8 +439,8 @@ be checked on the ticket.
 
 sub CheckRight {
     my $self = shift;
-    my $from = shift;
-    my $to = shift;
+    my $from = lc shift;
+    my $to = lc shift;
     if ( my $rights = $self->{'data'}{'rights'} ) {
         my $check =
             $rights->{ $from .' -> '. $to }
@@ -536,6 +536,7 @@ pairs:
 sub Actions {
     my $self = shift;
     my $from = shift || return ();
+    $from = lc $from;
 
     $self->FillCache unless keys %LIFECYCLES_CACHE;
 
diff --git a/lib/RT/Ticket.pm b/lib/RT/Ticket.pm
index 1945545..f68ebde 100644
--- a/lib/RT/Ticket.pm
+++ b/lib/RT/Ticket.pm
@@ -298,6 +298,7 @@ sub Create {
         $args{'Status'} = $cycle->DefaultOnCreate;
     }
 
+    $args{'Status'} = lc $args{'Status'};
     unless ( $cycle->IsValid( $args{'Status'} ) ) {
         return ( 0, 0,
             $self->loc("Status '[_1]' isn't a valid status for tickets in this queue.",
@@ -3164,7 +3165,7 @@ sub SetStatus {
 
     my $lifecycle = $self->QueueObj->Lifecycle;
 
-    my $new = $args{'Status'};
+    my $new = lc $args{'Status'};
     unless ( $lifecycle->IsValid( $new ) ) {
         return (0, $self->loc("Status '[_1]' isn't a valid status for tickets in this queue.", $self->loc($new)));
     }
@@ -3212,7 +3213,7 @@ sub SetStatus {
     #Actually update the status
     my ($val, $msg)= $self->_Set(
         Field           => 'Status',
-        Value           => $args{Status},
+        Value           => $new,
         TimeTaken       => 0,
         CheckACL        => 0,
         TransactionType => 'Status',
diff --git a/lib/RT/Tickets.pm b/lib/RT/Tickets.pm
index 3a1da4b..7331f1f 100644
--- a/lib/RT/Tickets.pm
+++ b/lib/RT/Tickets.pm
@@ -380,6 +380,8 @@ sub _EnumLimit {
         $value = $o->Id || 0;
     } elsif ( $field eq "Type" ) {
         $value = lc $value if $value =~ /^(ticket|approval|reminder)$/i;
+    } elsif ($field eq "Status") {
+        $value = lc $value;
     }
     $sb->_SQLLimit(
         FIELD    => $field,

commit 6eac780d28eb94eab22ccfd8e0dc5170ff450231
Author: Alex Vandiver <alexmv at bestpractical.com>
Date:   Wed May 1 18:01:23 2013 -0400

    Merge two loops over %LIFECYCLES_CACHE into one

diff --git a/lib/RT/Lifecycle.pm b/lib/RT/Lifecycle.pm
index 109b784..4a1e728 100644
--- a/lib/RT/Lifecycle.pm
+++ b/lib/RT/Lifecycle.pm
@@ -648,45 +648,50 @@ sub FillCache {
         active => [],
         inactive => [],
     );
-    foreach my $lifecycle ( values %LIFECYCLES_CACHE ) {
-        my @res;
+    foreach my $name ( keys %LIFECYCLES_CACHE ) {
+        next if $name eq "__maps__";
+        my $lifecycle = $LIFECYCLES_CACHE{$name};
+
+        my @statuses;
         foreach my $type ( qw(initial active inactive) ) {
-            push @{ $all{ $type } }, @{ $lifecycle->{ $type } || [] };
-            push @res,               @{ $lifecycle->{ $type } || [] };
+            for my $status (@{ $lifecycle->{ $type } || [] }) {
+                push @{ $all{ $type } }, $status;
+                push @statuses, $status;
+            }
         }
 
         my %seen;
-        @res = grep !$seen{ lc $_ }++, @res;
-        $lifecycle->{''} = \@res;
+        @statuses = grep !$seen{ $_ }++, @statuses;
+        $lifecycle->{''} = \@statuses;
 
         unless ( $lifecycle->{'transitions'}{''} ) {
-            $lifecycle->{'transitions'}{''} = [ grep $_ ne 'deleted', @res ];
+            $lifecycle->{'transitions'}{''} = [ grep $_ ne 'deleted', @statuses ];
         }
-    }
-    foreach my $type ( qw(initial active inactive), '' ) {
-        my %seen;
-        @{ $all{ $type } } = grep !$seen{ lc $_ }++, @{ $all{ $type } };
-        push @{ $all{''} }, @{ $all{ $type } } if $type;
-    }
-    $LIFECYCLES_CACHE{''} = \%all;
 
-    foreach my $lifecycle ( values %LIFECYCLES_CACHE ) {
-        my @res;
+        my @actions;
         if ( ref $lifecycle->{'actions'} eq 'HASH' ) {
             foreach my $k ( sort keys %{ $lifecycle->{'actions'} } ) {
-                push @res, $k, $lifecycle->{'actions'}{ $k };
+                push @actions, $k, $lifecycle->{'actions'}{ $k };
             }
         } elsif ( ref $lifecycle->{'actions'} eq 'ARRAY' ) {
-            @res = @{ $lifecycle->{'actions'} };
+            @actions = @{ $lifecycle->{'actions'} };
         }
 
-        my @tmp = splice @res;
-        while ( my ($transition, $info) = splice @tmp, 0, 2 ) {
+        $lifecycle->{'actions'} = [];
+        while ( my ($transition, $info) = splice @actions, 0, 2 ) {
             my ($from, $to) = split /\s*->\s*/, $transition, 2;
-            push @res, { %$info, from => $from, to => $to };
+            push @{ $lifecycle->{'actions'} },
+                { %$info, from => $from, to => $to };
         }
-        $lifecycle->{'actions'} = \@res;
     }
+
+    foreach my $type ( qw(initial active inactive), '' ) {
+        my %seen;
+        @{ $all{ $type } } = grep !$seen{ $_ }++, @{ $all{ $type } };
+        push @{ $all{''} }, @{ $all{ $type } } if $type;
+    }
+    $LIFECYCLES_CACHE{''} = \%all;
+
     return;
 }
 

commit 15857be24af0955e5eeb46e6bab96378704e1e09
Author: Alex Vandiver <alexmv at bestpractical.com>
Date:   Wed May 1 18:11:21 2013 -0400

    Force statuses to lower-case in lifecycles, to match ticket statuses
    
    Ticket statuses are now forced to be stored in lower-case; normalize all
    existing lifecycles to lower-case as well, to ensure that that existing
    workflows (including transitions and rights) continue to work on
    unmodified Lifecycle configurations.
    
    This does not resolve any scrips or other custom code which rely upon
    $ticket->Status eq "SomethingUpperCase", which still fail.

diff --git a/lib/RT/Lifecycle.pm b/lib/RT/Lifecycle.pm
index 4a1e728..abd9101 100644
--- a/lib/RT/Lifecycle.pm
+++ b/lib/RT/Lifecycle.pm
@@ -655,11 +655,26 @@ sub FillCache {
         my @statuses;
         foreach my $type ( qw(initial active inactive) ) {
             for my $status (@{ $lifecycle->{ $type } || [] }) {
-                push @{ $all{ $type } }, $status;
-                push @statuses, $status;
+                push @{ $all{ $type } }, lc $status;
+                push @statuses, lc $status;
             }
         }
 
+        # Lower-case for consistency
+        # ->{actions} are handled below
+        for my $state (keys %{ $lifecycle->{defaults} || {} }) {
+            $lifecycle->{defaults}{$state} = lc $lifecycle->{defaults}{$state};
+        }
+        for my $from (keys %{ $lifecycle->{transitions} || {} }) {
+            for my $status ( @{delete($lifecycle->{transitions}{$from}) || []} ) {
+                push @{ $lifecycle->{transitions}{lc $from} }, lc $status;
+            }
+        }
+        for my $schema (keys %{ $lifecycle->{rights} || {} }) {
+            $lifecycle->{rights}{lc $schema}
+                = delete $lifecycle->{rights}{$schema};
+        }
+
         my %seen;
         @statuses = grep !$seen{ $_ }++, @statuses;
         $lifecycle->{''} = \@statuses;
@@ -681,7 +696,15 @@ sub FillCache {
         while ( my ($transition, $info) = splice @actions, 0, 2 ) {
             my ($from, $to) = split /\s*->\s*/, $transition, 2;
             push @{ $lifecycle->{'actions'} },
-                { %$info, from => $from, to => $to };
+                { %$info, from => lc $from, to => lc $to };
+        }
+    }
+
+    # Lower-case the transition maps
+    for my $mapname (keys %{ $LIFECYCLES_CACHE{'__maps__'} || {} }) {
+        my $map = $LIFECYCLES_CACHE{'__maps__'}{$mapname};
+        for my $status (keys %{ $map }) {
+            $map->{lc $status} = lc delete $map->{$status};
         }
     }
 

commit 3af6cb052941d0fd6fab1f6bc28feaad85a1f8ee
Author: Alex Vandiver <alexmv at bestpractical.com>
Date:   Wed May 1 18:34:58 2013 -0400

    Preserve original case of defined statuses
    
    When normalizing lifecycle configurations, use the original case that
    they were written in for display.  This allows code which depended on
    the apparent casing of $ticket->Status to still continue to function,
    even while the back-end status is stored in lower-case.
    
    For simplicity of look-up, keys of the lifecycle are stored in
    lower-case, while lists of statuses are stored in their canonical case.

diff --git a/lib/RT/Lifecycle.pm b/lib/RT/Lifecycle.pm
index abd9101..2896b2d 100644
--- a/lib/RT/Lifecycle.pm
+++ b/lib/RT/Lifecycle.pm
@@ -540,7 +540,7 @@ sub Actions {
 
     $self->FillCache unless keys %LIFECYCLES_CACHE;
 
-    my @res = grep $_->{'from'} eq $from || ( $_->{'from'} eq '*' && $_->{'to'} ne $from ),
+    my @res = grep lc $_->{'from'} eq $from || ( $_->{'from'} eq '*' && lc $_->{'to'} ne $from ),
         @{ $self->{'data'}{'actions'} };
 
     # skip '* -> x' if there is '$from -> x'
@@ -634,6 +634,13 @@ sub ForLocalization {
 
 sub loc { return RT->SystemUser->loc( @_ ) }
 
+sub CanonicalCase {
+    my $self = shift;
+    my ($status) = @_;
+    return undef unless defined $status;
+    return $self->{data}{canonical_case}{lc $status};
+}
+
 sub FillCache {
     my $self = shift;
 
@@ -653,21 +660,26 @@ sub FillCache {
         my $lifecycle = $LIFECYCLES_CACHE{$name};
 
         my @statuses;
+        $lifecycle->{canonical_case} = {};
         foreach my $type ( qw(initial active inactive) ) {
             for my $status (@{ $lifecycle->{ $type } || [] }) {
-                push @{ $all{ $type } }, lc $status;
-                push @statuses, lc $status;
+                $lifecycle->{canonical_case}{lc $status} = $status;
+                push @{ $all{ $type } }, $status;
+                push @statuses, $status;
             }
         }
 
         # Lower-case for consistency
         # ->{actions} are handled below
         for my $state (keys %{ $lifecycle->{defaults} || {} }) {
-            $lifecycle->{defaults}{$state} = lc $lifecycle->{defaults}{$state};
+            my $status = $lifecycle->{defaults}{$state};
+            $lifecycle->{defaults}{$state} =
+                $lifecycle->{canonical_case}{lc $status};
         }
         for my $from (keys %{ $lifecycle->{transitions} || {} }) {
             for my $status ( @{delete($lifecycle->{transitions}{$from}) || []} ) {
-                push @{ $lifecycle->{transitions}{lc $from} }, lc $status;
+                push @{ $lifecycle->{transitions}{lc $from} },
+                    $lifecycle->{canonical_case}{lc $status};
             }
         }
         for my $schema (keys %{ $lifecycle->{rights} || {} }) {
@@ -696,7 +708,9 @@ sub FillCache {
         while ( my ($transition, $info) = splice @actions, 0, 2 ) {
             my ($from, $to) = split /\s*->\s*/, $transition, 2;
             push @{ $lifecycle->{'actions'} },
-                { %$info, from => lc $from, to => lc $to };
+                { %$info,
+                  from => $lifecycle->{canonical_case}{lc $from},
+                  to   => $lifecycle->{canonical_case}{lc $to} };
         }
     }
 
diff --git a/lib/RT/Ticket.pm b/lib/RT/Ticket.pm
index f68ebde..2ee9761 100644
--- a/lib/RT/Ticket.pm
+++ b/lib/RT/Ticket.pm
@@ -1731,7 +1731,7 @@ sub SetQueue {
         unless ( $old_lifecycle->HasMoveMap( $new_lifecycle ) ) {
             return ( 0, $self->loc("There is no mapping for statuses between these queues. Contact your system administrator.") );
         }
-        $new_status = $old_lifecycle->MoveMap( $new_lifecycle )->{ $self->Status };
+        $new_status = $old_lifecycle->MoveMap( $new_lifecycle )->{ lc $self->Status };
         return ( 0, $self->loc("Mapping between queues' lifecycles is incomplete. Contact your system administrator.") )
             unless $new_status;
     }
@@ -3135,7 +3135,12 @@ sub ValidateStatus {
     return 0;
 }
 
-
+sub Status {
+    my $self = shift;
+    my $value = $self->_Value( 'Status' );
+    return $value unless $self->QueueObj;
+    return $self->QueueObj->Lifecycle->CanonicalCase( $value );
+}
 
 =head2 SetStatus STATUS
 
diff --git a/lib/RT/Transaction.pm b/lib/RT/Transaction.pm
index 1832aef..da766c0 100644
--- a/lib/RT/Transaction.pm
+++ b/lib/RT/Transaction.pm
@@ -646,11 +646,14 @@ sub BriefDescription {
                 return ( $self->loc( "[_1] deleted", $obj_type ) );
             }
             else {
+                my $canon = $self->Object->can("QueueObj")
+                    ? sub { $self->Object->QueueObj->Lifecycle->CanonicalCase(@_) }
+                    : sub { return $_[0] };
                 return (
                     $self->loc(
                         "Status changed from [_1] to [_2]",
-                        "'" . $self->loc( $self->OldValue ) . "'",
-                        "'" . $self->loc( $self->NewValue ) . "'"
+                        "'" . $self->loc( $canon->($self->OldValue) ) . "'",
+                        "'" . $self->loc( $canon->($self->NewValue) ) . "'"
                     )
                 );
 

commit f19e3ea836a9e4612ae7256610d52014857a8c96
Author: Alex Vandiver <alexmv at bestpractical.com>
Date:   Wed May 1 19:03:14 2013 -0400

    Provide warnings of lifecycle misconfigurations
    
    As altering the lifecycle configuration to a standard casing requires
    that it be correct to begin with, provide feedback on lifecycle
    misconfigurations as the data structure is traversed.x

diff --git a/lib/RT/Lifecycle.pm b/lib/RT/Lifecycle.pm
index 2896b2d..6e3f639 100644
--- a/lib/RT/Lifecycle.pm
+++ b/lib/RT/Lifecycle.pm
@@ -638,7 +638,7 @@ sub CanonicalCase {
     my $self = shift;
     my ($status) = @_;
     return undef unless defined $status;
-    return $self->{data}{canonical_case}{lc $status};
+    return($self->{data}{canonical_case}{lc $status} || lc $status);
 }
 
 sub FillCache {
@@ -663,7 +663,11 @@ sub FillCache {
         $lifecycle->{canonical_case} = {};
         foreach my $type ( qw(initial active inactive) ) {
             for my $status (@{ $lifecycle->{ $type } || [] }) {
-                $lifecycle->{canonical_case}{lc $status} = $status;
+                if (exists $lifecycle->{canonical_case}{lc $status}) {
+                    warn "Duplicate status @{[lc $status]} in lifecycle $name";
+                } else {
+                    $lifecycle->{canonical_case}{lc $status} = $status;
+                }
                 push @{ $all{ $type } }, $status;
                 push @statuses, $status;
             }
@@ -673,17 +677,32 @@ sub FillCache {
         # ->{actions} are handled below
         for my $state (keys %{ $lifecycle->{defaults} || {} }) {
             my $status = $lifecycle->{defaults}{$state};
+            warn "Nonexistant status @{[lc $status]} in default states in $name lifecycle"
+                unless $lifecycle->{canonical_case}{lc $status};
             $lifecycle->{defaults}{$state} =
-                $lifecycle->{canonical_case}{lc $status};
+                $lifecycle->{canonical_case}{lc $status} || lc $status;
         }
         for my $from (keys %{ $lifecycle->{transitions} || {} }) {
+            warn "Nonexistant status @{[lc $from]} in transitions in $name lifecycle"
+                unless $from eq '' or $lifecycle->{canonical_case}{lc $from};
             for my $status ( @{delete($lifecycle->{transitions}{$from}) || []} ) {
+                warn "Nonexistant status @{[lc $status]} in transitions in $name lifecycle"
+                    unless $lifecycle->{canonical_case}{lc $status};
                 push @{ $lifecycle->{transitions}{lc $from} },
-                    $lifecycle->{canonical_case}{lc $status};
+                    $lifecycle->{canonical_case}{lc $status} || lc $status;
             }
         }
         for my $schema (keys %{ $lifecycle->{rights} || {} }) {
-            $lifecycle->{rights}{lc $schema}
+            my ($from, $to) = split /\s*->\s*/, $schema, 2;
+            unless ($from and $to) {
+                warn "Invalid right transition $schema in $name lifecycle";
+                next;
+            }
+            warn "Nonexistant status @{[lc $from]} in right transition in $name lifecycle"
+                unless $from eq '*' or $lifecycle->{canonical_case}{lc $from};
+            warn "Nonexistant status @{[lc $to]} in right transition in $name lifecycle"
+                unless $to eq '*' or $lifecycle->{canonical_case}{lc $to};
+            $lifecycle->{rights}{lc($from) . " -> " .lc($to)}
                 = delete $lifecycle->{rights}{$schema};
         }
 
@@ -707,17 +726,41 @@ sub FillCache {
         $lifecycle->{'actions'} = [];
         while ( my ($transition, $info) = splice @actions, 0, 2 ) {
             my ($from, $to) = split /\s*->\s*/, $transition, 2;
+            unless ($from and $to) {
+                warn "Invalid action status change $transition in $name lifecycle";
+                next;
+            }
+            warn "Nonexistant status @{[lc $from]} in action in $name lifecycle"
+                unless $from eq '*' or $lifecycle->{canonical_case}{lc $from};
+            warn "Nonexistant status @{[lc $to]} in action in $name lifecycle"
+                unless $to eq '*' or $lifecycle->{canonical_case}{lc $to};
             push @{ $lifecycle->{'actions'} },
                 { %$info,
-                  from => $lifecycle->{canonical_case}{lc $from},
-                  to   => $lifecycle->{canonical_case}{lc $to} };
+                  from => ($lifecycle->{canonical_case}{lc $from} || lc $from),
+                  to   => ($lifecycle->{canonical_case}{lc $to}   || lc $to),   };
         }
     }
 
     # Lower-case the transition maps
     for my $mapname (keys %{ $LIFECYCLES_CACHE{'__maps__'} || {} }) {
-        my $map = $LIFECYCLES_CACHE{'__maps__'}{$mapname};
+        my ($from, $to) = split /\s*->\s*/, $mapname, 2;
+        unless ($from and $to) {
+            warn "Invalid lifecycle mapping $mapname";
+            next;
+        }
+        warn "Nonexistant lifecycle $from in $mapname lifecycle map"
+            unless $LIFECYCLES_CACHE{$from};
+        warn "Nonexistant lifecycle $to in $mapname lifecycle map"
+            unless $LIFECYCLES_CACHE{$to};
+        my $map = delete $LIFECYCLES_CACHE{'__maps__'}{$mapname};
+        $LIFECYCLES_CACHE{'__maps__'}{"$from -> $to"} = $map;
         for my $status (keys %{ $map }) {
+            warn "Nonexistant status @{[lc $status]} in $from in $mapname lifecycle map"
+                if $LIFECYCLES_CACHE{$from}
+                    and not $LIFECYCLES_CACHE{$from}{canonical_case}{lc $status};
+            warn "Nonexistant status @{[lc $map->{$status}]} in $from in $mapname lifecycle map"
+                if $LIFECYCLES_CACHE{$to}
+                    and not $LIFECYCLES_CACHE{$to}{canonical_case}{lc $map->{$status}};
             $map->{lc $status} = lc delete $map->{$status};
         }
     }

commit 5dd6965cbb5e85e149ea22045627dcf6fb77fbe9
Merge: 871f4f6 e59d407
Author: Thomas Sibley <trs at bestpractical.com>
Date:   Thu May 2 15:05:06 2013 -0700

    Merge remote-tracking branch 'private/security/4.0/rt-predictable-tmpfile' into security/4.0.13-releng


commit fb2c9c07ebc86f5c7fef82d9704e7a6f3d7d69dc
Merge: 5dd6965 504d7bb
Author: Thomas Sibley <trs at bestpractical.com>
Date:   Thu May 2 15:05:18 2013 -0700

    Merge remote-tracking branch 'private/security/4.0/protect-columnmap-comp' into security/4.0.13-releng


commit 082cb7537dd96b16e7b37d68d798d4e971c11fa6
Merge: fb2c9c0 932e41b
Author: Thomas Sibley <trs at bestpractical.com>
Date:   Thu May 2 15:05:28 2013 -0700

    Merge remote-tracking branch 'private/security/4.0/escape-attachment-filename' into security/4.0.13-releng


commit a9f5f6aa1390279b41996c3c404223288bfc3e0e
Merge: 082cb75 6d0b9ae
Author: Thomas Sibley <trs at bestpractical.com>
Date:   Thu May 2 15:05:35 2013 -0700

    Merge remote-tracking branch 'private/security/4.0/deny-direct-callback-access' into security/4.0.13-releng


commit 51ab4ef85f4cabe2838bd078a138031ba690fd9c
Merge: a9f5f6a 00b7c82
Author: Thomas Sibley <trs at bestpractical.com>
Date:   Thu May 2 15:05:42 2013 -0700

    Merge remote-tracking branch 'private/security/4.0/attachment-filename-escaping' into security/4.0.13-releng


commit 03d71ac3ae9581ab3c416ad656f4312775e81a00
Author: Alex Vandiver <alexmv at bestpractical.com>
Date:   Fri Apr 19 23:45:17 2013 -0400

    Ensure that subjects cannot contain embedded newlines
    
    The default templates embed the ticket's subject in the outgoing Subject
    header -- but have no way to ensure that the subject does not contain
    newlines, which could be used to insert additional headers.
    Programmatically restricting templates from inserting newlines in the
    header section is both difficult and overly limiting, as there are valid
    reasons to do so.
    
    Strip all newlines from Subject headers.  While this does resolves the
    issue for all core RT templates, solving it more generally is
    unfortunately not feasable.

diff --git a/etc/upgrade/4.0.13/schema.Oracle b/etc/upgrade/4.0.13/schema.Oracle
new file mode 100644
index 0000000..fad9db0
--- /dev/null
+++ b/etc/upgrade/4.0.13/schema.Oracle
@@ -0,0 +1 @@
+UPDATE Tickets SET Subject = REPLACE(Subject,CHR(10),'');
diff --git a/etc/upgrade/4.0.13/schema.Pg b/etc/upgrade/4.0.13/schema.Pg
new file mode 100644
index 0000000..d748315
--- /dev/null
+++ b/etc/upgrade/4.0.13/schema.Pg
@@ -0,0 +1 @@
+UPDATE Tickets SET Subject = REPLACE(Subject,E'\n','');
diff --git a/etc/upgrade/4.0.13/schema.mysql b/etc/upgrade/4.0.13/schema.mysql
new file mode 100644
index 0000000..f6d59e1
--- /dev/null
+++ b/etc/upgrade/4.0.13/schema.mysql
@@ -0,0 +1 @@
+UPDATE Tickets SET Subject = REPLACE(Subject,'\n','');
diff --git a/lib/RT/Ticket.pm b/lib/RT/Ticket.pm
index cce5c56..751e03d 100644
--- a/lib/RT/Ticket.pm
+++ b/lib/RT/Ticket.pm
@@ -459,6 +459,8 @@ sub Create {
         }
     }
 
+    $args{'Subject'} =~ s/\n//g;
+
     $RT::Handle->BeginTransaction();
 
     my %params = (
@@ -1815,6 +1817,13 @@ sub QueueObj {
     return ($self->{_queue_obj});
 }
 
+sub SetSubject {
+    my $self = shift;
+    my $value = shift;
+    $value =~ s/\n//g;
+    return $self->_Set( Field => 'Subject', Value => $value );
+}
+
 =head2 SubjectTag
 
 Takes nothing. Returns SubjectTag for this ticket. Includes

commit 263e69cb71714125a1f6424899f882d4ef6bf058
Merge: 51ab4ef 03d71ac
Author: Thomas Sibley <trs at bestpractical.com>
Date:   Thu May 2 15:14:16 2013 -0700

    Merge remote-tracking branch 'private/security/4.0/subject-newlines' into security/4.0.13-releng
    
    Conflicts:
    	lib/RT/Ticket.pm

diff --cc lib/RT/Ticket.pm
index 1945545,751e03d..a457453
--- a/lib/RT/Ticket.pm
+++ b/lib/RT/Ticket.pm
@@@ -459,9 -459,8 +459,11 @@@ sub Create 
          }
      }
  
 +    $args{'Type'} = lc $args{'Type'}
 +        if $args{'Type'} =~ /^(ticket|approval|reminder)$/i;
 +
+     $args{'Subject'} =~ s/\n//g;
+ 
      $RT::Handle->BeginTransaction();
  
      my %params = (

commit 42ee5256ef9e8402b3f84b438da6ec61e70eb728
Merge: 263e69c 15c2568
Author: Thomas Sibley <trs at bestpractical.com>
Date:   Thu May 2 15:14:30 2013 -0700

    Merge remote-tracking branch 'private/security/4.0/instantiate-new-session-on-logout' into security/4.0.13-releng


commit 5ff6f8d0f2a1569e10e74bfa71aac8ec8849cf5a
Merge: 42ee525 8a9efd5
Author: Thomas Sibley <trs at bestpractical.com>
Date:   Thu May 2 15:14:39 2013 -0700

    Merge remote-tracking branch 'private/security/4.0/escape-makeclicky' into security/4.0.13-releng


commit f4dec8a834ec8a21221ab48ce13bbf56f80248b1
Merge: 5ff6f8d f19e3ea
Author: Thomas Sibley <trs at bestpractical.com>
Date:   Thu May 2 16:05:13 2013 -0700

    Merge remote-tracking branch 'private/security/4.0/status-casing' into security/4.0.13-releng
    
    Conflicts:
    	etc/upgrade/4.0.13/schema.Oracle
    	etc/upgrade/4.0.13/schema.Pg
    	etc/upgrade/4.0.13/schema.mysql
            Merged this branch's "UPDATE Tickets" statement into the
            existing one from security/4.0/subject-newlines.  One less table
            scan.

diff --cc etc/upgrade/4.0.13/schema.Oracle
index fad9db0,e43e40f..6ab7020
--- a/etc/upgrade/4.0.13/schema.Oracle
+++ b/etc/upgrade/4.0.13/schema.Oracle
@@@ -1,1 -1,2 +1,2 @@@
- UPDATE Tickets SET Subject = REPLACE(Subject,CHR(10),'');
 -UPDATE Tickets SET Status = LOWER(Status);
++UPDATE Tickets SET Subject = REPLACE(Subject,CHR(10),''), Status = LOWER(Status);
+ UPDATE Transactions SET OldValue = LOWER(OldValue), NewValue = LOWER(NewValue) WHERE Type = 'Status' AND Field = 'Status';
diff --cc etc/upgrade/4.0.13/schema.Pg
index d748315,e43e40f..8283f52
--- a/etc/upgrade/4.0.13/schema.Pg
+++ b/etc/upgrade/4.0.13/schema.Pg
@@@ -1,1 -1,2 +1,2 @@@
- UPDATE Tickets SET Subject = REPLACE(Subject,E'\n','');
 -UPDATE Tickets SET Status = LOWER(Status);
++UPDATE Tickets SET Subject = REPLACE(Subject,E'\n',''), Status = LOWER(Status);
+ UPDATE Transactions SET OldValue = LOWER(OldValue), NewValue = LOWER(NewValue) WHERE Type = 'Status' AND Field = 'Status';
diff --cc etc/upgrade/4.0.13/schema.mysql
index f6d59e1,e43e40f..03b54b5
--- a/etc/upgrade/4.0.13/schema.mysql
+++ b/etc/upgrade/4.0.13/schema.mysql
@@@ -1,1 -1,2 +1,2 @@@
- UPDATE Tickets SET Subject = REPLACE(Subject,'\n','');
 -UPDATE Tickets SET Status = LOWER(Status);
++UPDATE Tickets SET Subject = REPLACE(Subject,'\n',''), Status = LOWER(Status);
+ UPDATE Transactions SET OldValue = LOWER(OldValue), NewValue = LOWER(NewValue) WHERE Type = 'Status' AND Field = 'Status';

commit 90b6e7cb80c686b6bf41067029e75914748a4525
Author: Thomas Sibley <trs at bestpractical.com>
Date:   Wed May 8 11:46:33 2013 -0700

    Correct a typo in a lifecycle lint warning message
    
    The typo doesn't affect lint logic, it just makes the log message
    misleading/confusing.

diff --git a/lib/RT/Lifecycle.pm b/lib/RT/Lifecycle.pm
index 6e3f639..c905282 100644
--- a/lib/RT/Lifecycle.pm
+++ b/lib/RT/Lifecycle.pm
@@ -758,7 +758,7 @@ sub FillCache {
             warn "Nonexistant status @{[lc $status]} in $from in $mapname lifecycle map"
                 if $LIFECYCLES_CACHE{$from}
                     and not $LIFECYCLES_CACHE{$from}{canonical_case}{lc $status};
-            warn "Nonexistant status @{[lc $map->{$status}]} in $from in $mapname lifecycle map"
+            warn "Nonexistant status @{[lc $map->{$status}]} in $to in $mapname lifecycle map"
                 if $LIFECYCLES_CACHE{$to}
                     and not $LIFECYCLES_CACHE{$to}{canonical_case}{lc $map->{$status}};
             $map->{lc $status} = lc delete $map->{$status};

commit b8105f1750064fd5d52be2a4e4810bd471c7e443
Merge: 1d1ff68 90b6e7c
Author: Thomas Sibley <trs at bestpractical.com>
Date:   Wed May 22 11:32:41 2013 -0700

    Merge branch 'security/4.0.13-releng' into 4.0-trunk
    
    Conflicts:
    	share/html/Elements/EditCustomFieldBinary

diff --cc share/html/Elements/EditCustomFieldBinary
index f9719b7,c74bfd0..3222554
--- a/share/html/Elements/EditCustomFieldBinary
+++ b/share/html/Elements/EditCustomFieldBinary
@@@ -47,7 -47,7 +47,7 @@@
  %# END BPS TAGGED BLOCK }}}
  % while ( $Values and my $value = $Values->Next ) {
  %# XXX - let user download the file(s) here?
- <input type="checkbox" name="<%$NamePrefix%><%$CustomField->Id%>-DeleteValueIds" class="checkbox CF-<%$CustomField->id%>-Edit" value="<% $value->Id %>" /><a href="<%RT->Config->Get('WebPath')%>/Download/CustomFieldValue/<% $value->Id %>/<% $value->Content %>"><% $value->Content %></a><br />
 -<input type="checkbox" class="checkbox" name="<%$NamePrefix%><%$CustomField->Id%>-DeleteValueIds" class="CF-<%$CustomField->id%>-Edit" value="<% $value->Id %>" /><a href="<%RT->Config->Get('WebPath')%>/Download/CustomFieldValue/<% $value->Id %>/<% $value->Content |un %>"><% $value->Content %></a><br />
++<input type="checkbox" name="<%$NamePrefix%><%$CustomField->Id%>-DeleteValueIds" class="checkbox CF-<%$CustomField->id%>-Edit" value="<% $value->Id %>" /><a href="<%RT->Config->Get('WebPath')%>/Download/CustomFieldValue/<% $value->Id %>/<% $value->Content |un %>"><% $value->Content %></a><br />
  % }
  % if (!$MaxValues || !$Values || $Values->Count < $MaxValues) {
  <input type="file" name="<% $NamePrefix %><% $CustomField->Id %>-Upload" class="CF-<%$CustomField->id%>-Edit" />

-----------------------------------------------------------------------


More information about the Rt-commit mailing list