[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