From c24d6e2242ae0e026684b8f95decf156aba6e75e Mon Sep 17 00:00:00 2001
From: Ivan Kohler
Date: Thu, 7 Jun 2012 16:55:45 -0700
Subject: [PATCH] rt 4.0.6
---
FS/FS/Mason.pm | 2 +-
rt/Makefile.in | 2 +-
rt/bin/rt | 7 +-
rt/bin/rt-mailgate.in | 20 +-
rt/bin/rt.in | 7 +-
rt/configure | 149 +++----
rt/docs/UPGRADING-4.0 | 23 ++
rt/docs/hacking.pod | 10 +-
rt/docs/security.pod | 15 +
rt/docs/web_deployment.pod | 13 +
rt/etc/RT_Config.pm.in | 72 +++-
rt/etc/schema.mysql | 2 +-
rt/etc/upgrade/vulnerable-passwords.in | 3 +
rt/lib/RT/ACL.pm | 3 +
rt/lib/RT/Action/CreateTickets.pm | 13 +-
rt/lib/RT/Action/SendEmail.pm | 9 +-
rt/lib/RT/Article.pm | 11 +
rt/lib/RT/Attachments.pm | 11 +-
rt/lib/RT/Class.pm | 1 +
rt/lib/RT/Config.pm | 1 +
rt/lib/RT/CustomField.pm | 80 +++-
rt/lib/RT/Dashboard/Mailer.pm | 5 +-
rt/lib/RT/Date.pm | 30 +-
rt/lib/RT/Generated.pm | 2 +-
rt/lib/RT/Graph/Tickets.pm | 10 +-
rt/lib/RT/Group.pm | 10 +
rt/lib/RT/Groups.pm | 8 +
rt/lib/RT/Handle.pm | 20 +-
rt/lib/RT/I18N.pm | 51 ++-
rt/lib/RT/Installer.pm | 2 +-
rt/lib/RT/Interface/Email.pm | 44 ++-
rt/lib/RT/Interface/Web.pm | 364 +++++++++++++++---
rt/lib/RT/Interface/Web/Handler.pm | 12 +-
rt/lib/RT/Interface/Web/QueryBuilder/Tree.pm | 14 +-
rt/lib/RT/Lifecycle.pm | 22 ++
rt/lib/RT/Links.pm | 14 +-
rt/lib/RT/ObjectCustomField.pm | 12 +
rt/lib/RT/ObjectCustomFieldValue.pm | 8 +-
rt/lib/RT/Queue.pm | 12 +
rt/lib/RT/Reminders.pm | 6 +-
rt/lib/RT/Report/Tickets.pm | 8 +-
rt/lib/RT/Report/Tickets/Entry.pm | 4 +
rt/lib/RT/Scrip.pm | 24 +-
rt/lib/RT/SearchBuilder.pm | 19 +-
rt/lib/RT/Shredder.pm | 2 +
rt/lib/RT/Shredder/Plugin.pm | 1 +
rt/lib/RT/Shredder/Queue.pm | 1 +
rt/lib/RT/Template.pm | 24 ++
rt/lib/RT/Test.pm | 52 ++-
rt/lib/RT/Test/Web.pm | 15 +-
rt/lib/RT/Ticket.pm | 16 +-
rt/lib/RT/Tickets.pm | 30 +-
rt/lib/RT/Transaction.pm | 18 +-
rt/lib/RT/URI.pm | 2 +-
rt/lib/RT/URI/fsck_com_article.pm | 2 +-
rt/lib/RT/User.pm | 74 ++--
rt/lib/RT/Users.pm | 8 +
rt/sbin/rt-server.fcgi.in | 1 +
rt/sbin/rt-server.in | 1 +
rt/sbin/rt-shredder.in | 2 +-
rt/sbin/rt-test-dependencies.in | 7 +-
rt/sbin/standalone_httpd | 1 +
rt/sbin/standalone_httpd.in | 1 +
rt/share/html/Admin/Articles/Elements/Topics | 2 +-
rt/share/html/Admin/CustomFields/Modify.html | 4 +-
rt/share/html/Admin/Elements/EditCustomFields | 3 +
rt/share/html/Admin/Elements/EditRights | 6 +-
rt/share/html/Admin/Elements/Portal | 2 +-
.../html/Admin/Elements/SelectNewGroupMembers | 8 +-
rt/share/html/Admin/Groups/index.html | 2 +-
rt/share/html/Admin/Tools/Queries.html | 4 +-
.../html/Admin/Tools/Shredder/Dumps/dhandler | 5 +-
.../Tools/Shredder/Elements/Error/NoStorage | 2 +-
.../Shredder/Elements/Object/RT--Attachment | 2 +-
.../Tools/Shredder/Elements/Object/RT--Ticket | 2 +-
.../Tools/Shredder/Elements/Object/RT--User | 2 +-
rt/share/html/Admin/Users/Modify.html | 8 +-
rt/share/html/Admin/Users/index.html | 2 +-
.../html/Approvals/Elements/PendingMyApproval | 4 +-
rt/share/html/Articles/Article/Edit.html | 1 +
.../html/Articles/Article/Elements/EditTopics | 55 ++-
.../Articles/Article/ExtractIntoClass.html | 2 +-
rt/share/html/Articles/Topics.html | 249 +++++-------
.../html/Elements/CollectionAsTable/Header | 4 +-
rt/share/html/Elements/CollectionList | 2 +-
rt/share/html/Elements/CollectionListPaging | 12 +-
rt/share/html/Elements/ColumnMap | 10 +-
rt/share/html/Elements/CreateTicket | 2 +-
rt/share/html/Elements/EditCustomField | 2 +-
.../html/Elements/EditCustomFieldAutocomplete | 13 +-
rt/share/html/Elements/EditCustomFieldSelect | 6 +-
rt/share/html/Elements/Error | 2 +-
rt/share/html/Elements/Header | 3 +-
rt/share/html/Elements/HeaderJavascript | 4 +-
rt/share/html/Elements/MessageBox | 15 +-
.../html/Elements/RT__CustomField/ColumnMap | 8 +-
.../html/Elements/RT__Dashboard/ColumnMap | 2 +-
rt/share/html/Elements/RT__Queue/ColumnMap | 6 +-
rt/share/html/Elements/SelectOwner | 10 +-
.../html/Elements/SelectOwnerAutocomplete | 4 +-
rt/share/html/Elements/SelectStatus | 14 +-
rt/share/html/Elements/ShowCustomFields | 10 +-
rt/share/html/Elements/ShowLink | 11 +-
rt/share/html/Elements/ShowSearch | 8 +-
rt/share/html/Elements/ShowUser | 2 +-
rt/share/html/Elements/Submit | 14 +-
rt/share/html/Elements/Tabs | 3 +
.../Helpers/Autocomplete/CustomFieldValues | 44 ++-
rt/share/html/Helpers/Toggle/ShowRequestor | 4 +-
rt/share/html/Install/DatabaseType.html | 2 +-
rt/share/html/Install/Finish.html | 2 +-
rt/share/html/Install/Initialize.html | 2 +-
rt/share/html/Install/index.html | 2 +-
rt/share/html/NoAuth/Logout.html | 2 +-
rt/share/html/NoAuth/css/aileron/InHeader | 3 -
rt/share/html/NoAuth/css/aileron/boxes.css | 4 +
rt/share/html/NoAuth/css/aileron/msie-pie.css | 58 ---
rt/share/html/NoAuth/css/images/PIE.htc | 77 ----
rt/share/html/NoAuth/css/web2/InHeader | 3 -
rt/share/html/NoAuth/css/web2/msie-pie.css | 60 ---
rt/share/html/NoAuth/js/titlebox-state.js | 2 +-
rt/share/html/NoAuth/js/userautocomplete.js | 2 +-
rt/share/html/NoAuth/js/util.js | 4 +-
rt/share/html/Prefs/Search.html | 2 +-
rt/share/html/REST/1.0/Forms/ticket/default | 24 +-
rt/share/html/REST/1.0/Forms/ticket/links | 3 +-
.../html/REST/1.0/Forms/transaction/default | 3 -
rt/share/html/REST/1.0/ticket/link | 5 +-
rt/share/html/Search/Build.html | 6 +-
rt/share/html/Search/Chart.html | 2 +-
.../html/Search/Elements/BuildFormatString | 12 +-
rt/share/html/Search/Elements/Chart | 4 +-
rt/share/html/Search/Elements/PickBasics | 7 +-
rt/share/html/Search/Elements/PickCFs | 20 +-
rt/share/html/Search/Elements/PickCriteria | 4 +-
rt/share/html/Search/Results.html | 12 +-
rt/share/html/Search/Simple.html | 10 +-
rt/share/html/SelfService/Elements/MyRequests | 22 +-
rt/share/html/SelfService/index.html | 2 +
rt/share/html/Ticket/Create.html | 45 +--
rt/share/html/Ticket/Display.html | 6 +-
rt/share/html/Ticket/Elements/Bookmark | 2 +-
.../html/Ticket/Elements/ClickToShowHistory | 2 +-
rt/share/html/Ticket/Elements/FoldStanzaJS | 2 +-
rt/share/html/Ticket/Elements/Reminders | 11 +-
rt/share/html/Ticket/Elements/ShowHistory | 9 +-
rt/share/html/Ticket/Elements/ShowRequestor | 4 +-
rt/share/html/Ticket/Elements/UpdateCc | 6 +-
rt/share/html/Ticket/GnuPG.html | 2 +-
.../Graphs/Elements/EditGraphProperties | 2 +-
.../html/Ticket/Graphs/Elements/ShowGraph | 1 +
rt/share/html/Ticket/Graphs/dhandler | 1 +
rt/share/html/Ticket/ModifyLinks.html | 2 +-
rt/share/html/Widgets/ComboBox | 4 +-
rt/share/html/Widgets/TitleBoxStart | 2 +-
rt/share/html/index.html | 2 +-
rt/share/html/l | 2 +-
rt/share/html/m/_elements/footer | 10 -
rt/share/html/m/ticket/create | 58 ++-
rt/share/html/m/ticket/show | 12 +-
rt/share/html/m/tickets/search | 2 +-
rt/t/api/date.t | 10 +-
rt/t/api/tickets.t | 15 +-
rt/t/lifecycles/basics.t | 2 +-
rt/t/mail/mime_decoding.t | 28 +-
rt/t/web/case-sensitivity.t | 2 +-
rt/t/web/query_builder.t | 33 +-
rt/t/web/redirect-after-login.t | 6 +-
rt/t/web/rest.t | 83 +++-
rt/t/web/scrub.t | 4 +-
rt/t/web/ticket_forward.t | 35 ++
rt/t/web/ticket_links.t | 65 +++-
172 files changed, 1804 insertions(+), 1020 deletions(-)
delete mode 100644 rt/share/html/NoAuth/css/aileron/msie-pie.css
delete mode 100644 rt/share/html/NoAuth/css/images/PIE.htc
delete mode 100644 rt/share/html/NoAuth/css/web2/msie-pie.css
diff --git a/FS/FS/Mason.pm b/FS/FS/Mason.pm
index 4556b0ec7..f6ad714d3 100644
--- a/FS/FS/Mason.pm
+++ b/FS/FS/Mason.pm
@@ -580,7 +580,7 @@ sub mason_interps {
},
compiler => HTML::Mason::Compiler::ToObject->new(
default_escape_flags => 'h',
- allow_globals => [qw(%session)],
+ allow_globals => [qw(%session $DECODED_ARGS)],
),
);
diff --git a/rt/Makefile.in b/rt/Makefile.in
index 98c2c30e5..b415a06db 100644
--- a/rt/Makefile.in
+++ b/rt/Makefile.in
@@ -355,7 +355,7 @@ test:
parallel-test: test-parallel
test-parallel:
- RT_TEST_PARALLEL=1 $(PERL) "-MApp::Prove" -e 'my $$p = App::Prove->new(); $$p->process_args("-wlrj5","--state=slow,save", "t"); $$p->run()'
+ RT_TEST_PARALLEL=1 $(PERL) "-MApp::Prove" -e 'my $$p = App::Prove->new(); $$p->process_args("-wlrj5","--state=slow,save", "t"); exit( $$p->run() ? 0 : 1 )'
regression-reset-db: force-dropdb
$(PERL) -I$(LOCAL_LIB_PATH) -I$(RT_LIB_PATH) sbin/rt-setup-database --action init --dba-password ''
diff --git a/rt/bin/rt b/rt/bin/rt
index 01e4a190e..32f459a7e 100755
--- a/rt/bin/rt
+++ b/rt/bin/rt
@@ -905,11 +905,6 @@ sub link {
if (@ARGV == 3) {
my ($from, $rel, $to) = @ARGV;
- if ($from !~ /^\d+$/ || $to !~ /^\d+$/) {
- my $bad = $from =~ /^\d+$/ ? $to : $from;
- whine "Invalid $type ID '$bad' specified.";
- $bad = 1;
- }
if (($type eq "ticket") && ( ! exists $ltypes{lc $rel})) {
whine "Invalid link '$rel' for type $type specified.";
$bad = 1;
@@ -1710,7 +1705,7 @@ sub prettyshow {
}
print "$k->{Content}\n" if exists $k->{Content} and
$k->{Content} !~ /to have no content$/ and
- $k->{Type} ne 'EmailRecord';
+ ($k->{Type}||'') ne 'EmailRecord';
print "$k->{Attachments}\n" if exists $k->{Attachments} and
$k->{Attachments};
}
diff --git a/rt/bin/rt-mailgate.in b/rt/bin/rt-mailgate.in
index b125a94af..72cada613 100644
--- a/rt/bin/rt-mailgate.in
+++ b/rt/bin/rt-mailgate.in
@@ -172,7 +172,6 @@ sub setup_session {
my $self = shift;
my $opts = shift;
my %post_params;
- $post_params{SessionType} = 'REST'; # Surpress login box
foreach (qw(queue action)) {
$post_params{$_} = $opts->{$_} if defined $opts->{$_};
}
@@ -253,20 +252,13 @@ sub check_failure {
my $r = shift;
return if $r->is_success;
- # This ordinarily oughtn't to be able to happen, suggests a bug in RT.
- # So only load these heavy modules when they're needed.
- require HTML::TreeBuilder;
- require HTML::FormatText;
+ # XXX TODO 4.2: Remove the multi-line error strings in favor of something more concise
+ print STDERR <<" ERROR";
+An Error Occurred
+=================
- my $error = $r->error_as_HTML;
- my $tree = HTML::TreeBuilder->new->parse($error);
- $tree->eof;
-
- # It'll be a cold day in hell before RT sends out bounces in HTML
- my $formatter =
- HTML::FormatText->new( leftmargin => 0,
- rightmargin => 50, );
- print STDERR $formatter->format($tree);
+@{[ $r->status_line ]}
+ ERROR
print STDERR "\n$0: undefined server error\n" if $opts->{'debug'};
return $self->tempfail();
}
diff --git a/rt/bin/rt.in b/rt/bin/rt.in
index 5e1c05366..e54a07add 100644
--- a/rt/bin/rt.in
+++ b/rt/bin/rt.in
@@ -905,11 +905,6 @@ sub link {
if (@ARGV == 3) {
my ($from, $rel, $to) = @ARGV;
- if ($from !~ /^\d+$/ || $to !~ /^\d+$/) {
- my $bad = $from =~ /^\d+$/ ? $to : $from;
- whine "Invalid $type ID '$bad' specified.";
- $bad = 1;
- }
if (($type eq "ticket") && ( ! exists $ltypes{lc $rel})) {
whine "Invalid link '$rel' for type $type specified.";
$bad = 1;
@@ -1710,7 +1705,7 @@ sub prettyshow {
}
print "$k->{Content}\n" if exists $k->{Content} and
$k->{Content} !~ /to have no content$/ and
- $k->{Type} ne 'EmailRecord';
+ ($k->{Type}||'') ne 'EmailRecord';
print "$k->{Attachments}\n" if exists $k->{Attachments} and
$k->{Attachments};
}
diff --git a/rt/configure b/rt/configure
index 4decdbdf4..1862c5fe6 100755
--- a/rt/configure
+++ b/rt/configure
@@ -1,7 +1,7 @@
#! /bin/sh
# From configure.ac Revision.
# Guess values for system-dependent variables and create Makefiles.
-# Generated by GNU Autoconf 2.68 for RT rt-4.0.5.
+# Generated by GNU Autoconf 2.67 for RT rt-4.0.6.
#
# Report bugs to .
#
@@ -92,7 +92,6 @@ fi
IFS=" "" $as_nl"
# Find who we are. Look in the path if we contain no directory separator.
-as_myself=
case $0 in #((
*[\\/]* ) as_myself=$0 ;;
*) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
@@ -217,18 +216,11 @@ IFS=$as_save_IFS
# We cannot yet assume a decent shell, so we have to provide a
# neutralization value for shells without unset; and this also
# works around shells that cannot unset nonexistent variables.
- # Preserve -v and -x to the replacement shell.
BASH_ENV=/dev/null
ENV=/dev/null
(unset BASH_ENV) >/dev/null 2>&1 && unset BASH_ENV ENV
export CONFIG_SHELL
- case $- in # ((((
- *v*x* | *x*v* ) as_opts=-vx ;;
- *v* ) as_opts=-v ;;
- *x* ) as_opts=-x ;;
- * ) as_opts= ;;
- esac
- exec "$CONFIG_SHELL" $as_opts "$as_myself" ${1+"$@"}
+ exec "$CONFIG_SHELL" "$as_myself" ${1+"$@"}
fi
if test x$as_have_required = xno; then :
@@ -560,8 +552,8 @@ MAKEFLAGS=
# Identity of this package.
PACKAGE_NAME='RT'
PACKAGE_TARNAME='rt'
-PACKAGE_VERSION='rt-4.0.5'
-PACKAGE_STRING='RT rt-4.0.5'
+PACKAGE_VERSION='rt-4.0.6'
+PACKAGE_STRING='RT rt-4.0.6'
PACKAGE_BUGREPORT='rt-bugs@bestpractical.com'
PACKAGE_URL=''
@@ -1173,7 +1165,7 @@ Try \`$0 --help' for more information"
$as_echo "$as_me: WARNING: you should use --build, --host, --target" >&2
expr "x$ac_option" : ".*[^-._$as_cr_alnum]" >/dev/null &&
$as_echo "$as_me: WARNING: invalid host type: $ac_option" >&2
- : "${build_alias=$ac_option} ${host_alias=$ac_option} ${target_alias=$ac_option}"
+ : ${build_alias=$ac_option} ${host_alias=$ac_option} ${target_alias=$ac_option}
;;
esac
@@ -1311,7 +1303,7 @@ if test "$ac_init_help" = "long"; then
# Omit some internal or obsolete options to make the list less imposing.
# This message is too long to be a string in the A/UX 3.1 sh.
cat <<_ACEOF
-\`configure' configures RT rt-4.0.5 to adapt to many kinds of systems.
+\`configure' configures RT rt-4.0.6 to adapt to many kinds of systems.
Usage: $0 [OPTION]... [VAR=VALUE]...
@@ -1372,7 +1364,7 @@ fi
if test -n "$ac_init_help"; then
case $ac_init_help in
- short | recursive ) echo "Configuration of RT rt-4.0.5:";;
+ short | recursive ) echo "Configuration of RT rt-4.0.6:";;
esac
cat <<\_ACEOF
@@ -1496,8 +1488,8 @@ fi
test -n "$ac_init_help" && exit $ac_status
if $ac_init_version; then
cat <<\_ACEOF
-RT configure rt-4.0.5
-generated by GNU Autoconf 2.68
+RT configure rt-4.0.6
+generated by GNU Autoconf 2.67
Copyright (C) 2010 Free Software Foundation, Inc.
This configure script is free software; the Free Software Foundation
@@ -1543,7 +1535,7 @@ sed 's/^/| /' conftest.$ac_ext >&5
ac_retval=1
fi
- eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno
+ eval $as_lineno_stack; test "x$as_lineno_stack" = x && { as_lineno=; unset as_lineno;}
as_fn_set_status $ac_retval
} # ac_fn_c_try_compile
@@ -1589,7 +1581,7 @@ fi
# interfere with the next link command; also delete a directory that is
# left behind by Apple's compiler. We do this before executing the actions.
rm -rf conftest.dSYM conftest_ipa8_conftest.oo
- eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno
+ eval $as_lineno_stack; test "x$as_lineno_stack" = x && { as_lineno=; unset as_lineno;}
as_fn_set_status $ac_retval
} # ac_fn_c_try_link
@@ -1597,8 +1589,8 @@ cat >config.log <<_ACEOF
This file contains any messages produced by compilers while
running configure, to aid debugging if configure makes a mistake.
-It was created by RT $as_me rt-4.0.5, which was
-generated by GNU Autoconf 2.68. Invocation command line was
+It was created by RT $as_me rt-4.0.6, which was
+generated by GNU Autoconf 2.67. Invocation command line was
$ $0 $@
@@ -1856,7 +1848,7 @@ $as_echo "$as_me: loading site script $ac_site_file" >&6;}
|| { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
$as_echo "$as_me: error: in \`$ac_pwd':" >&2;}
as_fn_error $? "failed to load site script $ac_site_file
-See \`config.log' for more details" "$LINENO" 5; }
+See \`config.log' for more details" "$LINENO" 5 ; }
fi
done
@@ -1954,7 +1946,7 @@ rt_version_major=4
rt_version_minor=0
-rt_version_patch=5
+rt_version_patch=6
test "x$rt_version_major" = 'x' && rt_version_major=0
test "x$rt_version_minor" = 'x' && rt_version_minor=0
@@ -2006,7 +1998,7 @@ ac_configure="$SHELL $ac_aux_dir/configure" # Please don't use this var.
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for a BSD-compatible install" >&5
$as_echo_n "checking for a BSD-compatible install... " >&6; }
if test -z "$INSTALL"; then
-if ${ac_cv_path_install+:} false; then :
+if test "${ac_cv_path_install+set}" = set; then :
$as_echo_n "(cached) " >&6
else
as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
@@ -2087,7 +2079,7 @@ test -z "$INSTALL_DATA" && INSTALL_DATA='${INSTALL} -m 644'
set dummy perl; ac_word=$2
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
$as_echo_n "checking for $ac_word... " >&6; }
-if ${ac_cv_path_PERL+:} false; then :
+if test "${ac_cv_path_PERL+set}" = set; then :
$as_echo_n "(cached) " >&6
else
case $PERL in
@@ -2808,7 +2800,7 @@ if test -n "$ac_tool_prefix"; then
set dummy ${ac_tool_prefix}gcc; ac_word=$2
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
$as_echo_n "checking for $ac_word... " >&6; }
-if ${ac_cv_prog_CC+:} false; then :
+if test "${ac_cv_prog_CC+set}" = set; then :
$as_echo_n "(cached) " >&6
else
if test -n "$CC"; then
@@ -2848,7 +2840,7 @@ if test -z "$ac_cv_prog_CC"; then
set dummy gcc; ac_word=$2
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
$as_echo_n "checking for $ac_word... " >&6; }
-if ${ac_cv_prog_ac_ct_CC+:} false; then :
+if test "${ac_cv_prog_ac_ct_CC+set}" = set; then :
$as_echo_n "(cached) " >&6
else
if test -n "$ac_ct_CC"; then
@@ -2901,7 +2893,7 @@ if test -z "$CC"; then
set dummy ${ac_tool_prefix}cc; ac_word=$2
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
$as_echo_n "checking for $ac_word... " >&6; }
-if ${ac_cv_prog_CC+:} false; then :
+if test "${ac_cv_prog_CC+set}" = set; then :
$as_echo_n "(cached) " >&6
else
if test -n "$CC"; then
@@ -2941,7 +2933,7 @@ if test -z "$CC"; then
set dummy cc; ac_word=$2
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
$as_echo_n "checking for $ac_word... " >&6; }
-if ${ac_cv_prog_CC+:} false; then :
+if test "${ac_cv_prog_CC+set}" = set; then :
$as_echo_n "(cached) " >&6
else
if test -n "$CC"; then
@@ -3000,7 +2992,7 @@ if test -z "$CC"; then
set dummy $ac_tool_prefix$ac_prog; ac_word=$2
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
$as_echo_n "checking for $ac_word... " >&6; }
-if ${ac_cv_prog_CC+:} false; then :
+if test "${ac_cv_prog_CC+set}" = set; then :
$as_echo_n "(cached) " >&6
else
if test -n "$CC"; then
@@ -3044,7 +3036,7 @@ do
set dummy $ac_prog; ac_word=$2
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
$as_echo_n "checking for $ac_word... " >&6; }
-if ${ac_cv_prog_ac_ct_CC+:} false; then :
+if test "${ac_cv_prog_ac_ct_CC+set}" = set; then :
$as_echo_n "(cached) " >&6
else
if test -n "$ac_ct_CC"; then
@@ -3099,7 +3091,7 @@ fi
test -z "$CC" && { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
$as_echo "$as_me: error: in \`$ac_pwd':" >&2;}
as_fn_error $? "no acceptable C compiler found in \$PATH
-See \`config.log' for more details" "$LINENO" 5; }
+See \`config.log' for more details" "$LINENO" 5 ; }
# Provide some information about the compiler.
$as_echo "$as_me:${as_lineno-$LINENO}: checking for C compiler version" >&5
@@ -3214,7 +3206,7 @@ sed 's/^/| /' conftest.$ac_ext >&5
{ { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
$as_echo "$as_me: error: in \`$ac_pwd':" >&2;}
as_fn_error 77 "C compiler cannot create executables
-See \`config.log' for more details" "$LINENO" 5; }
+See \`config.log' for more details" "$LINENO" 5 ; }
else
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
$as_echo "yes" >&6; }
@@ -3257,7 +3249,7 @@ else
{ { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
$as_echo "$as_me: error: in \`$ac_pwd':" >&2;}
as_fn_error $? "cannot compute suffix of executables: cannot compile and link
-See \`config.log' for more details" "$LINENO" 5; }
+See \`config.log' for more details" "$LINENO" 5 ; }
fi
rm -f conftest conftest$ac_cv_exeext
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_exeext" >&5
@@ -3316,7 +3308,7 @@ $as_echo "$ac_try_echo"; } >&5
$as_echo "$as_me: error: in \`$ac_pwd':" >&2;}
as_fn_error $? "cannot run C compiled programs.
If you meant to cross compile, use \`--host'.
-See \`config.log' for more details" "$LINENO" 5; }
+See \`config.log' for more details" "$LINENO" 5 ; }
fi
fi
fi
@@ -3327,7 +3319,7 @@ rm -f conftest.$ac_ext conftest$ac_cv_exeext conftest.out
ac_clean_files=$ac_clean_files_save
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for suffix of object files" >&5
$as_echo_n "checking for suffix of object files... " >&6; }
-if ${ac_cv_objext+:} false; then :
+if test "${ac_cv_objext+set}" = set; then :
$as_echo_n "(cached) " >&6
else
cat confdefs.h - <<_ACEOF >conftest.$ac_ext
@@ -3368,7 +3360,7 @@ sed 's/^/| /' conftest.$ac_ext >&5
{ { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
$as_echo "$as_me: error: in \`$ac_pwd':" >&2;}
as_fn_error $? "cannot compute suffix of object files: cannot compile
-See \`config.log' for more details" "$LINENO" 5; }
+See \`config.log' for more details" "$LINENO" 5 ; }
fi
rm -f conftest.$ac_cv_objext conftest.$ac_ext
fi
@@ -3378,7 +3370,7 @@ OBJEXT=$ac_cv_objext
ac_objext=$OBJEXT
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether we are using the GNU C compiler" >&5
$as_echo_n "checking whether we are using the GNU C compiler... " >&6; }
-if ${ac_cv_c_compiler_gnu+:} false; then :
+if test "${ac_cv_c_compiler_gnu+set}" = set; then :
$as_echo_n "(cached) " >&6
else
cat confdefs.h - <<_ACEOF >conftest.$ac_ext
@@ -3415,7 +3407,7 @@ ac_test_CFLAGS=${CFLAGS+set}
ac_save_CFLAGS=$CFLAGS
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether $CC accepts -g" >&5
$as_echo_n "checking whether $CC accepts -g... " >&6; }
-if ${ac_cv_prog_cc_g+:} false; then :
+if test "${ac_cv_prog_cc_g+set}" = set; then :
$as_echo_n "(cached) " >&6
else
ac_save_c_werror_flag=$ac_c_werror_flag
@@ -3493,7 +3485,7 @@ else
fi
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $CC option to accept ISO C89" >&5
$as_echo_n "checking for $CC option to accept ISO C89... " >&6; }
-if ${ac_cv_prog_cc_c89+:} false; then :
+if test "${ac_cv_prog_cc_c89+set}" = set; then :
$as_echo_n "(cached) " >&6
else
ac_cv_prog_cc_c89=no
@@ -3591,7 +3583,7 @@ ac_compiler_gnu=$ac_cv_c_compiler_gnu
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for aginitlib in -lgraph" >&5
$as_echo_n "checking for aginitlib in -lgraph... " >&6; }
-if ${ac_cv_lib_graph_aginitlib+:} false; then :
+if test "${ac_cv_lib_graph_aginitlib+set}" = set; then :
$as_echo_n "(cached) " >&6
else
ac_check_lib_save_LIBS=$LIBS
@@ -3625,7 +3617,7 @@ LIBS=$ac_check_lib_save_LIBS
fi
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_graph_aginitlib" >&5
$as_echo "$ac_cv_lib_graph_aginitlib" >&6; }
-if test "x$ac_cv_lib_graph_aginitlib" = xyes; then :
+if test "x$ac_cv_lib_graph_aginitlib" = x""yes; then :
RT_GRAPHVIZ="1"
fi
@@ -3651,7 +3643,7 @@ fi
set dummy gdlib-config; ac_word=$2
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
$as_echo_n "checking for $ac_word... " >&6; }
-if ${ac_cv_prog_RT_GD+:} false; then :
+if test "${ac_cv_prog_RT_GD+set}" = set; then :
$as_echo_n "(cached) " >&6
else
if test -n "$RT_GD"; then
@@ -3707,7 +3699,7 @@ fi
set dummy gpg; ac_word=$2
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
$as_echo_n "checking for $ac_word... " >&6; }
-if ${ac_cv_prog_RT_GPG+:} false; then :
+if test "${ac_cv_prog_RT_GPG+set}" = set; then :
$as_echo_n "(cached) " >&6
else
if test -n "$RT_GPG"; then
@@ -3992,21 +3984,10 @@ $as_echo "$as_me: WARNING: cache variable $ac_var contains a newline" >&2;} ;;
:end' >>confcache
if diff "$cache_file" confcache >/dev/null 2>&1; then :; else
if test -w "$cache_file"; then
- if test "x$cache_file" != "x/dev/null"; then
+ test "x$cache_file" != "x/dev/null" &&
{ $as_echo "$as_me:${as_lineno-$LINENO}: updating cache $cache_file" >&5
$as_echo "$as_me: updating cache $cache_file" >&6;}
- if test ! -f "$cache_file" || test -h "$cache_file"; then
- cat confcache >"$cache_file"
- else
- case $cache_file in #(
- */* | ?:*)
- mv -f confcache "$cache_file"$$ &&
- mv -f "$cache_file"$$ "$cache_file" ;; #(
- *)
- mv -f confcache "$cache_file" ;;
- esac
- fi
- fi
+ cat confcache >$cache_file
else
{ $as_echo "$as_me:${as_lineno-$LINENO}: not updating unwritable cache $cache_file" >&5
$as_echo "$as_me: not updating unwritable cache $cache_file" >&6;}
@@ -4074,7 +4055,7 @@ LTLIBOBJS=$ac_ltlibobjs
-: "${CONFIG_STATUS=./config.status}"
+: ${CONFIG_STATUS=./config.status}
ac_write_fail=0
ac_clean_files_save=$ac_clean_files
ac_clean_files="$ac_clean_files $CONFIG_STATUS"
@@ -4175,7 +4156,6 @@ fi
IFS=" "" $as_nl"
# Find who we are. Look in the path if we contain no directory separator.
-as_myself=
case $0 in #((
*[\\/]* ) as_myself=$0 ;;
*) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
@@ -4482,8 +4462,8 @@ cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1
# report actual input values of CONFIG_FILES etc. instead of their
# values after options handling.
ac_log="
-This file was extended by RT $as_me rt-4.0.5, which was
-generated by GNU Autoconf 2.68. Invocation command line was
+This file was extended by RT $as_me rt-4.0.6, which was
+generated by GNU Autoconf 2.67. Invocation command line was
CONFIG_FILES = $CONFIG_FILES
CONFIG_HEADERS = $CONFIG_HEADERS
@@ -4535,8 +4515,8 @@ _ACEOF
cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
ac_cs_config="`$as_echo "$ac_configure_args" | sed 's/^ //; s/[\\""\`\$]/\\\\&/g'`"
ac_cs_version="\\
-RT config.status rt-4.0.5
-configured by $0, generated by GNU Autoconf 2.68,
+RT config.status rt-4.0.6
+configured by $0, generated by GNU Autoconf 2.67,
with options \\"\$ac_cs_config\\"
Copyright (C) 2010 Free Software Foundation, Inc.
@@ -4678,7 +4658,7 @@ do
"t/data/configs/apache2.2+mod_perl.conf") CONFIG_FILES="$CONFIG_FILES t/data/configs/apache2.2+mod_perl.conf" ;;
"t/data/configs/apache2.2+fastcgi.conf") CONFIG_FILES="$CONFIG_FILES t/data/configs/apache2.2+fastcgi.conf" ;;
- *) as_fn_error $? "invalid argument: \`$ac_config_target'" "$LINENO" 5;;
+ *) as_fn_error $? "invalid argument: \`$ac_config_target'" "$LINENO" 5 ;;
esac
done
@@ -4699,10 +4679,9 @@ fi
# after its creation but before its name has been assigned to `$tmp'.
$debug ||
{
- tmp= ac_tmp=
+ tmp=
trap 'exit_status=$?
- : "${ac_tmp:=$tmp}"
- { test ! -d "$ac_tmp" || rm -fr "$ac_tmp"; } && exit $exit_status
+ { test -z "$tmp" || test ! -d "$tmp" || rm -fr "$tmp"; } && exit $exit_status
' 0
trap 'as_fn_exit 1' 1 2 13 15
}
@@ -4710,13 +4689,12 @@ $debug ||
{
tmp=`(umask 077 && mktemp -d "./confXXXXXX") 2>/dev/null` &&
- test -d "$tmp"
+ test -n "$tmp" && test -d "$tmp"
} ||
{
tmp=./conf$$-$RANDOM
(umask 077 && mkdir "$tmp")
} || as_fn_error $? "cannot create a temporary directory in ." "$LINENO" 5
-ac_tmp=$tmp
# Set up the scripts for CONFIG_FILES section.
# No need to generate them if there are no CONFIG_FILES.
@@ -4738,7 +4716,7 @@ else
ac_cs_awk_cr=$ac_cr
fi
-echo 'BEGIN {' >"$ac_tmp/subs1.awk" &&
+echo 'BEGIN {' >"$tmp/subs1.awk" &&
_ACEOF
@@ -4766,7 +4744,7 @@ done
rm -f conf$$subs.sh
cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
-cat >>"\$ac_tmp/subs1.awk" <<\\_ACAWK &&
+cat >>"\$tmp/subs1.awk" <<\\_ACAWK &&
_ACEOF
sed -n '
h
@@ -4814,7 +4792,7 @@ t delim
rm -f conf$$subs.awk
cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
_ACAWK
-cat >>"\$ac_tmp/subs1.awk" <<_ACAWK &&
+cat >>"\$tmp/subs1.awk" <<_ACAWK &&
for (key in S) S_is_set[key] = 1
FS = ""
@@ -4846,7 +4824,7 @@ if sed "s/$ac_cr//" < /dev/null > /dev/null 2>&1; then
sed "s/$ac_cr\$//; s/$ac_cr/$ac_cs_awk_cr/g"
else
cat
-fi < "$ac_tmp/subs1.awk" > "$ac_tmp/subs.awk" \
+fi < "$tmp/subs1.awk" > "$tmp/subs.awk" \
|| as_fn_error $? "could not setup config files machinery" "$LINENO" 5
_ACEOF
@@ -4886,7 +4864,7 @@ do
esac
case $ac_mode$ac_tag in
:[FHL]*:*);;
- :L* | :C*:*) as_fn_error $? "invalid tag \`$ac_tag'" "$LINENO" 5;;
+ :L* | :C*:*) as_fn_error $? "invalid tag \`$ac_tag'" "$LINENO" 5 ;;
:[FH]-) ac_tag=-:-;;
:[FH]*) ac_tag=$ac_tag:$ac_tag.in;;
esac
@@ -4905,7 +4883,7 @@ do
for ac_f
do
case $ac_f in
- -) ac_f="$ac_tmp/stdin";;
+ -) ac_f="$tmp/stdin";;
*) # Look for the file first in the build tree, then in the source tree
# (if the path is not absolute). The absolute path cannot be DOS-style,
# because $ac_f cannot contain `:'.
@@ -4914,7 +4892,7 @@ do
[\\/$]*) false;;
*) test -f "$srcdir/$ac_f" && ac_f="$srcdir/$ac_f";;
esac ||
- as_fn_error 1 "cannot find input file: \`$ac_f'" "$LINENO" 5;;
+ as_fn_error 1 "cannot find input file: \`$ac_f'" "$LINENO" 5 ;;
esac
case $ac_f in *\'*) ac_f=`$as_echo "$ac_f" | sed "s/'/'\\\\\\\\''/g"`;; esac
as_fn_append ac_file_inputs " '$ac_f'"
@@ -4940,8 +4918,8 @@ $as_echo "$as_me: creating $ac_file" >&6;}
esac
case $ac_tag in
- *:-:* | *:-) cat >"$ac_tmp/stdin" \
- || as_fn_error $? "could not create $ac_file" "$LINENO" 5 ;;
+ *:-:* | *:-) cat >"$tmp/stdin" \
+ || as_fn_error $? "could not create $ac_file" "$LINENO" 5 ;;
esac
;;
esac
@@ -5071,22 +5049,21 @@ s&@abs_top_builddir@&$ac_abs_top_builddir&;t t
s&@INSTALL@&$ac_INSTALL&;t t
$ac_datarootdir_hack
"
-eval sed \"\$ac_sed_extra\" "$ac_file_inputs" | $AWK -f "$ac_tmp/subs.awk" \
- >$ac_tmp/out || as_fn_error $? "could not create $ac_file" "$LINENO" 5
+eval sed \"\$ac_sed_extra\" "$ac_file_inputs" | $AWK -f "$tmp/subs.awk" >$tmp/out \
+ || as_fn_error $? "could not create $ac_file" "$LINENO" 5
test -z "$ac_datarootdir_hack$ac_datarootdir_seen" &&
- { ac_out=`sed -n '/\${datarootdir}/p' "$ac_tmp/out"`; test -n "$ac_out"; } &&
- { ac_out=`sed -n '/^[ ]*datarootdir[ ]*:*=/p' \
- "$ac_tmp/out"`; test -z "$ac_out"; } &&
+ { ac_out=`sed -n '/\${datarootdir}/p' "$tmp/out"`; test -n "$ac_out"; } &&
+ { ac_out=`sed -n '/^[ ]*datarootdir[ ]*:*=/p' "$tmp/out"`; test -z "$ac_out"; } &&
{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $ac_file contains a reference to the variable \`datarootdir'
which seems to be undefined. Please make sure it is defined" >&5
$as_echo "$as_me: WARNING: $ac_file contains a reference to the variable \`datarootdir'
which seems to be undefined. Please make sure it is defined" >&2;}
- rm -f "$ac_tmp/stdin"
+ rm -f "$tmp/stdin"
case $ac_file in
- -) cat "$ac_tmp/out" && rm -f "$ac_tmp/out";;
- *) rm -f "$ac_file" && mv "$ac_tmp/out" "$ac_file";;
+ -) cat "$tmp/out" && rm -f "$tmp/out";;
+ *) rm -f "$ac_file" && mv "$tmp/out" "$ac_file";;
esac \
|| as_fn_error $? "could not create $ac_file" "$LINENO" 5
;;
diff --git a/rt/docs/UPGRADING-4.0 b/rt/docs/UPGRADING-4.0
index a9301348e..4b64d2e72 100644
--- a/rt/docs/UPGRADING-4.0
+++ b/rt/docs/UPGRADING-4.0
@@ -106,3 +106,26 @@ with
database level.
*******
+
+UPGRADING FROM 4.0.5 and earlier - Changes:
+
+The fix for an attribute truncation bug on MySQL requires a small ALTER TABLE.
+Be sure you run `make upgrade-database` to apply this change automatically.
+The bug primarily manifested when uploading large logos in the theme editor on
+MySQL. Refer to etc/upgrade/4.0.6/schema.mysql for the actual ALTER TABLE that
+will be run.
+
+*******
+The web-based query builder now uses Queue limits to restrict the set of
+displayed statuses and owners. As part of this change, the %cfqueues
+parameter was renamed to %Queues; if you have local modifications to any
+of the following Mason templates, this feature will not function
+correctly:
+
+ share/html/Elements/SelectOwner
+ share/html/Elements/SelectStatus
+ share/html/Prefs/Search.html
+ share/html/Search/Build.html
+ share/html/Search/Elements/BuildFormatString
+ share/html/Search/Elements/PickCFs
+ share/html/Search/Elements/PickCriteria
diff --git a/rt/docs/hacking.pod b/rt/docs/hacking.pod
index 8aa84fd01..396c5623d 100644
--- a/rt/docs/hacking.pod
+++ b/rt/docs/hacking.pod
@@ -186,11 +186,11 @@ which will be significantly faster:
make test-parallel
-The C<*-trunk> and C branches are expected to be passing always
-be passing all tests. While it is acceptable to break tests in an
-intermediate commit, a branch which does not pass tests will not be
-merged. Ideally, commits which fix a bug should also include a testcase
-which fails before the fix and succeeds after.
+The C<*-trunk> and C branches are expected to always be passing
+all tests. While it is acceptable to break tests in an intermediate
+commit, a branch which does not pass tests will not be merged. Ideally,
+commits which fix a bug should also include a testcase which fails
+before the fix and succeeds after.
diff --git a/rt/docs/security.pod b/rt/docs/security.pod
index b8650e05d..620f8687c 100644
--- a/rt/docs/security.pod
+++ b/rt/docs/security.pod
@@ -9,6 +9,21 @@ key).
More information is available at L.
+
+=head2 RT's security process
+
+After a security vulnerability is reported to Best Practical and
+verified, we attempt to resolve it in as timely a fashion as possible.
+Best Practical support customers will be notified before we disclose the
+information to the public. All security announcements will be sent to
+C, which includes
+C and C.
+
+As the tests for security vulnerabilities are often nearly identical to
+working exploits, sensitive tests will be embargoed for a period of six
+months before being added to the public RT repository.
+
+
=head2 Security tips for running RT
=over
diff --git a/rt/docs/web_deployment.pod b/rt/docs/web_deployment.pod
index 65065c5cd..4c3f73fb5 100644
--- a/rt/docs/web_deployment.pod
+++ b/rt/docs/web_deployment.pod
@@ -67,6 +67,19 @@ spontaneously logged in as other users in the system.
=head3 mod_fcgid
+B: Before mod_fcgid 2.3.6, the maximum request size was 1GB.
+Starting in 2.3.6, this is now 128Kb. This is unlikely to be large
+enough for any RT install that handles attachments. You can read more
+about FcgidMaxRequestLen at
+L
+
+Most distributions will have a mod_fcgid.conf or similar file with
+mod_fcgid configurations and you should add:
+
+ FcgidMaxRequestLen 1073741824
+
+to return to the old default.
+
### Optional apache logs for RT
# Ensure that your log rotation scripts know about these files
diff --git a/rt/etc/RT_Config.pm.in b/rt/etc/RT_Config.pm.in
index 925f0cabe..de7660a19 100644
--- a/rt/etc/RT_Config.pm.in
+++ b/rt/etc/RT_Config.pm.in
@@ -350,13 +350,8 @@ Set($StoreLoops, undef);
C<$MaxAttachmentSize> sets the maximum size (in bytes) of attachments
stored in the database.
-For MySQL and Oracle, we set this size to 10 megabytes. If you're
-running a PostgreSQL version earlier than 7.1, you will need to drop
-this to 8192. (8k)
-
=cut
-
Set($MaxAttachmentSize, 10_000_000);
=item C<$TruncateLongAttachments>
@@ -892,6 +887,8 @@ Set($CanonicalizeRedirectURLs, 0);
A list of JavaScript files to be included in head. Removing any of
the default entries is not suggested.
+If you're a plugin author, refer to RT->AddJavaScript.
+
=cut
Set(@JSFiles, qw/
@@ -928,6 +925,8 @@ directory, or from http://www.crockford.com/javascript/jsmin.html
A list of additional CSS files to be included in head.
+If you're a plugin author, refer to RT->AddStyleSheets.
+
=cut
Set(@CSSFiles, qw//);
@@ -1789,8 +1788,50 @@ This disables RT's clickjacking protection.
Set($Framebusting, 1);
+=item C<$RestrictReferrer>
+
+If set to a false value, the HTTP C (sic) header will not be
+checked to ensure that requests come from RT's own domain. As RT allows
+for GET requests to alter state, disabling this opens RT up to
+cross-site request forgery (CSRF) attacks.
+
+=cut
+
+Set($RestrictReferrer, 1);
+
+=item C<$RestrictLoginReferrer>
+
+If set to a false value, RT will allow the user to log in from any link
+or request, merely by passing in C and C parameters; setting
+it to a true value forces all logins to come from the login box, so the
+user is aware that they are being logged in. The default is off, for
+backwards compatability.
+
+=cut
+
+Set($RestrictLoginReferrer, 0);
+
+=item C<$ReferrerWhitelist>
+
+This is a list of hostname:port combinations that RT will treat as being
+part of RT's domain. This is particularly useful if you access RT as
+multiple hostnames or have an external auth system that needs to
+redirect back to RT once authentication is complete.
+
+ Set(@ReferrerWhitelist, qw(www.example.com:443 www3.example.com:80));
+
+If the "RT has detected a possible cross-site request forgery" error is triggered
+by a host:port sent by your browser that you believe should be valid, you can copy
+the host:port from the error message into this list.
+
+=cut
+
+Set(@ReferrerWhitelist, qw());
+
=back
+
+
=head1 Authorization and user configuration
=over 4
@@ -1856,10 +1897,9 @@ Set($AutoCreate, undef);
=item C<$WebSessionClass>
-C<$WebSessionClass> is the class you wish to use for managing
-Sessions. It defaults to use your SQL database, but if you are using
-MySQL 3.x and plans to use non-ascii Queue names, uncomment and add
-this line to F to prevent session corruption.
+C<$WebSessionClass> is the class you wish to use for managing sessions.
+It defaults to use your SQL database, except on Oracle, where it
+defaults to files on disk.
=cut
@@ -2206,6 +2246,14 @@ be changed to this value.
When an approval is denied, the status of depending tickets will
be changed to this value.
+=item reminder_on_open
+
+When a reminder is opened, the status will be changed to this value.
+
+=item reminder_on_resolve
+
+When a reminder is resolved, the status will be changed to this value.
+
=back
=head2 Transitions between statuses and UI actions
@@ -2352,6 +2400,8 @@ Set(%Lifecycles,
on_merge => 'resolved',
approved => 'open',
denied => 'rejected',
+ reminder_on_open => 'open',
+ reminder_on_resolve => 'resolved',
},
transitions => {
@@ -2425,6 +2475,8 @@ Set(%Lifecycles,
defaults => {
on_create => 'new',
on_merge => 'resolved',
+ reminder_on_open => 'open',
+ reminder_on_resolve => 'resolved',
},
transitions => {
@@ -2529,7 +2581,7 @@ Set(%AdminSearchResultFormat,
Queues =>
q{'__id__/TITLE:#'}
.q{,'__Name__/TITLE:Name'}
- .q{,__Description__,__Address__,__Priority__,__DefaultDueIn__,__Disabled__},
+ .q{,__Description__,__Address__,__Priority__,__DefaultDueIn__,'__Disabled__,__Lifecycle__},
Groups =>
q{'__id__/TITLE:#'}
diff --git a/rt/etc/schema.mysql b/rt/etc/schema.mysql
index c313aaf54..9ed0337aa 100644
--- a/rt/etc/schema.mysql
+++ b/rt/etc/schema.mysql
@@ -413,7 +413,7 @@ CREATE TABLE Attributes (
id INTEGER NOT NULL AUTO_INCREMENT,
Name varchar(255) NULL ,
Description varchar(255) NULL ,
- Content BLOB,
+ Content LONGBLOB,
ContentType varchar(16) CHARACTER SET ascii,
ObjectType varchar(64) CHARACTER SET ascii,
ObjectId integer, # foreign key to anything
diff --git a/rt/etc/upgrade/vulnerable-passwords.in b/rt/etc/upgrade/vulnerable-passwords.in
index 728786fb6..a3d719c31 100755
--- a/rt/etc/upgrade/vulnerable-passwords.in
+++ b/rt/etc/upgrade/vulnerable-passwords.in
@@ -89,6 +89,9 @@ push @{$users->{'restrictions'}{ "main.Password" }}, "AND", {
value => '40',
};
+# we want to update passwords on disabled users
+$users->{'find_disabled_rows'} = 1;
+
my $count = $users->Count;
if ($count == 0) {
print "No users with unsalted or weak cryptography found.\n";
diff --git a/rt/lib/RT/ACL.pm b/rt/lib/RT/ACL.pm
index d7c9ef2a8..49a7f1d64 100755
--- a/rt/lib/RT/ACL.pm
+++ b/rt/lib/RT/ACL.pm
@@ -182,6 +182,9 @@ sub LimitToPrincipal {
ALIAS2 => $cgm,
FIELD2 => 'GroupId'
);
+ $self->Limit( ALIAS => $cgm,
+ FIELD => 'Disabled',
+ VALUE => 0 );
$self->Limit( ALIAS => $cgm,
FIELD => 'MemberId',
OPERATOR => '=',
diff --git a/rt/lib/RT/Action/CreateTickets.pm b/rt/lib/RT/Action/CreateTickets.pm
index c26e2eb1d..31489c8ff 100644
--- a/rt/lib/RT/Action/CreateTickets.pm
+++ b/rt/lib/RT/Action/CreateTickets.pm
@@ -325,9 +325,19 @@ sub Prepare {
}
+ my $active = 0;
+ if ( $self->TemplateObj->Type eq 'Perl' ) {
+ $active = 1;
+ } else {
+ RT->Logger->info(sprintf(
+ "Template #%d is type %s. You most likely want to use a Perl template instead.",
+ $self->TemplateObj->id, $self->TemplateObj->Type
+ ));
+ }
+
$self->Parse(
Content => $self->TemplateObj->Content,
- _ActiveContent => 1
+ _ActiveContent => $active,
);
return 1;
@@ -1171,6 +1181,7 @@ sub UpdateCustomFields {
my $cf = $1;
my $CustomFieldObj = RT::CustomField->new($self->CurrentUser);
+ $CustomFieldObj->SetContextObject( $ticket );
$CustomFieldObj->LoadById($cf);
my @values;
diff --git a/rt/lib/RT/Action/SendEmail.pm b/rt/lib/RT/Action/SendEmail.pm
index e2aa00bb6..4ae1a8b66 100755
--- a/rt/lib/RT/Action/SendEmail.pm
+++ b/rt/lib/RT/Action/SendEmail.pm
@@ -348,7 +348,7 @@ sub AddAttachments {
$MIMEObj->head->delete('RT-Attach-Message');
- my $attachments = RT::Attachments->new(RT->SystemUser);
+ my $attachments = RT::Attachments->new( RT->SystemUser );
$attachments->Limit(
FIELD => 'TransactionId',
VALUE => $self->TransactionObj->Id
@@ -408,6 +408,10 @@ sub AddAttachment {
my $attach = shift;
my $MIMEObj = shift || $self->TemplateObj->MIMEObj;
+ # $attach->TransactionObj may not always be $self->TransactionObj
+ return unless $attach->Id
+ and $attach->TransactionObj->CurrentUserCanSee;
+
# ->attach expects just the disposition type; extract it if we have the header
my $disp = ($attach->GetHeader('Content-Disposition') || '')
=~ /^\s*(inline|attachment)/i ? $1 : undef;
@@ -471,8 +475,7 @@ sub AddTicket {
my $self = shift;
my $tid = shift;
- # XXX: we need a current user here, but who is current user?
- my $attachs = RT::Attachments->new(RT->SystemUser);
+ my $attachs = RT::Attachments->new( $self->TransactionObj->CreatorObj );
my $txn_alias = $attachs->TransactionAlias;
$attachs->Limit( ALIAS => $txn_alias, FIELD => 'Type', VALUE => 'Create' );
$attachs->Limit(
diff --git a/rt/lib/RT/Article.pm b/rt/lib/RT/Article.pm
index 7310241ee..24b952ad4 100644
--- a/rt/lib/RT/Article.pm
+++ b/rt/lib/RT/Article.pm
@@ -543,6 +543,17 @@ sub CurrentUserHasRight {
}
+=head2 CurrentUserCanSee
+
+Returns true if the current user can see the article, using ShowArticle
+
+=cut
+
+sub CurrentUserCanSee {
+ my $self = shift;
+ return $self->CurrentUserHasRight('ShowArticle');
+}
+
# }}}
# {{{ _Set
diff --git a/rt/lib/RT/Attachments.pm b/rt/lib/RT/Attachments.pm
index c640b206e..2bdbc244c 100755
--- a/rt/lib/RT/Attachments.pm
+++ b/rt/lib/RT/Attachments.pm
@@ -227,15 +227,12 @@ sub Next {
my $Attachment = $self->SUPER::Next;
return $Attachment unless $Attachment;
- my $txn = $Attachment->TransactionObj;
- if ( $txn->__Value('Type') eq 'Comment' ) {
- return $Attachment if $txn->CurrentUserHasRight('ShowTicketComments');
- } elsif ( $txn->CurrentUserHasRight('ShowTicket') ) {
+ if ( $Attachment->TransactionObj->CurrentUserCanSee ) {
return $Attachment;
+ } else {
+ # If the user doesn't have the right to show this ticket
+ return $self->Next;
}
-
- # If the user doesn't have the right to show this ticket
- return $self->Next;
}
diff --git a/rt/lib/RT/Class.pm b/rt/lib/RT/Class.pm
index bb694ce9c..3906b9fed 100644
--- a/rt/lib/RT/Class.pm
+++ b/rt/lib/RT/Class.pm
@@ -275,6 +275,7 @@ sub ArticleCustomFields {
my $cfs = RT::CustomFields->new( $self->CurrentUser );
if ( $self->CurrentUserHasRight('SeeClass') ) {
+ $cfs->SetContextObject( $self );
$cfs->LimitToGlobalOrObjectId( $self->Id );
$cfs->LimitToLookupType( RT::Article->CustomFieldLookupType );
$cfs->ApplySortOrder;
diff --git a/rt/lib/RT/Config.pm b/rt/lib/RT/Config.pm
index cc47df35a..c56d4c602 100644
--- a/rt/lib/RT/Config.pm
+++ b/rt/lib/RT/Config.pm
@@ -620,6 +620,7 @@ our %META = (
}
}
},
+ ReferrerWhitelist => { Type => 'ARRAY' },
ResolveDefaultUpdateType => {
PostLoadCheck => sub {
my $self = shift;
diff --git a/rt/lib/RT/CustomField.pm b/rt/lib/RT/CustomField.pm
index 095caa52f..263bde877 100644
--- a/rt/lib/RT/CustomField.pm
+++ b/rt/lib/RT/CustomField.pm
@@ -474,10 +474,12 @@ sub LoadByName {
}
# if we're looking for a queue by name, make it a number
- if ( defined $args{'Queue'} && $args{'Queue'} =~ /\D/ ) {
+ if ( defined $args{'Queue'} && ($args{'Queue'} =~ /\D/ || !$self->ContextObject) ) {
my $QueueObj = RT::Queue->new( $self->CurrentUser );
$QueueObj->Load( $args{'Queue'} );
$args{'Queue'} = $QueueObj->Id;
+ $self->SetContextObject( $QueueObj )
+ unless $self->ContextObject;
}
# XXX - really naive implementation. Slow. - not really. still just one query
@@ -535,6 +537,8 @@ sub Values {
# if the user has no rights, return an empty object
if ( $self->id && $self->CurrentUserHasRight( 'SeeCustomField') ) {
$cf_values->LimitToCustomField( $self->Id );
+ } else {
+ $cf_values->Limit( FIELD => 'id', VALUE => 0, SUBCLAUSE => 'acl' );
}
return ($cf_values);
}
@@ -890,7 +894,77 @@ sub ContextObject {
my $self = shift;
return $self->{'context_object'};
}
-
+
+sub ValidContextType {
+ my $self = shift;
+ my $class = shift;
+
+ my %valid;
+ $valid{$_}++ for split '-', $self->LookupType;
+ delete $valid{'RT::Transaction'};
+
+ return $valid{$class};
+}
+
+=head2 LoadContextObject
+
+Takes an Id for a Context Object and loads the right kind of RT::Object
+for this particular Custom Field (based on the LookupType) and returns it.
+This is a good way to ensure you don't try to use a Queue as a Context
+Object on a User Custom Field.
+
+=cut
+
+sub LoadContextObject {
+ my $self = shift;
+ my $type = shift;
+ my $contextid = shift;
+
+ unless ( $self->ValidContextType($type) ) {
+ RT->Logger->debug("Invalid ContextType $type for Custom Field ".$self->Id);
+ return;
+ }
+
+ my $context_object = $type->new( $self->CurrentUser );
+ my ($id, $msg) = $context_object->LoadById( $contextid );
+ unless ( $id ) {
+ RT->Logger->debug("Invalid ContextObject id: $msg");
+ return;
+ }
+ return $context_object;
+}
+
+=head2 ValidateContextObject
+
+Ensure that a given ContextObject applies to this Custom Field.
+For custom fields that are assigned to Queues or to Classes, this checks that the Custom
+Field is actually applied to that objects. For Global Custom Fields, it returns true
+as long as the Object is of the right type, because you may be using
+your permissions on a given Queue of Class to see a Global CF.
+For CFs that are only applied Globally, you don't need a ContextObject.
+
+=cut
+
+sub ValidateContextObject {
+ my $self = shift;
+ my $object = shift;
+
+ return 1 if $self->IsApplied(0);
+
+ # global only custom fields don't have objects
+ # that should be used as context objects.
+ return if $self->ApplyGlobally;
+
+ # Otherwise, make sure we weren't passed a user object that we're
+ # supposed to treat as a queue.
+ return unless $self->ValidContextType(ref $object);
+
+ # Check that it is applied correctly
+ my ($applied_to) = grep {ref($_) eq $self->RecordClassFromLookupType} ($object, $object->ACLEquivalenceObjects);
+ return unless $applied_to;
+ return $self->IsApplied($applied_to->id);
+}
+
sub _Set {
my $self = shift;
@@ -1702,6 +1776,7 @@ sub SetBasedOn {
unless defined $value and length $value;
my $cf = RT::CustomField->new( $self->CurrentUser );
+ $cf->SetContextObject( $self->ContextObject );
$cf->Load( ref $value ? $value->id : $value );
return (0, "Permission denied")
@@ -1719,6 +1794,7 @@ sub BasedOnObj {
my $self = shift;
my $obj = RT::CustomField->new( $self->CurrentUser );
+ $obj->SetContextObject( $self->ContextObject );
if ( $self->BasedOn ) {
$obj->Load( $self->BasedOn );
}
diff --git a/rt/lib/RT/Dashboard/Mailer.pm b/rt/lib/RT/Dashboard/Mailer.pm
index 85589787e..40b53b111 100644
--- a/rt/lib/RT/Dashboard/Mailer.pm
+++ b/rt/lib/RT/Dashboard/Mailer.pm
@@ -252,7 +252,7 @@ SUMMARY
$content = HTML::RewriteAttributes::Links->rewrite(
$content,
- RT->Config->Get('WebURL') . '/Dashboards/Render.html',
+ RT->Config->Get('WebURL') . 'Dashboards/Render.html',
);
$self->EmailDashboard(
@@ -447,6 +447,9 @@ sub BuildEmail {
autohandler_name => '', # disable forced login and more
data_dir => $data_dir,
);
+ $mason->set_escape( h => \&RT::Interface::Web::EscapeUTF8 );
+ $mason->set_escape( u => \&RT::Interface::Web::EscapeURI );
+ $mason->set_escape( j => \&RT::Interface::Web::EscapeJS );
}
return $mason;
}
diff --git a/rt/lib/RT/Date.pm b/rt/lib/RT/Date.pm
index 7a97fbe0e..442c7701d 100644
--- a/rt/lib/RT/Date.pm
+++ b/rt/lib/RT/Date.pm
@@ -604,6 +604,10 @@ sub Get
my $self = shift;
my %args = (Format => 'ISO', @_);
my $formatter = $args{'Format'};
+ unless ( $self->ValidFormatter($formatter) ) {
+ RT->Logger->warning("Invalid date formatter '$formatter', falling back to ISO");
+ $formatter = 'ISO';
+ }
$formatter = 'ISO' unless $self->can($formatter);
return $self->$formatter( %args );
}
@@ -642,6 +646,20 @@ sub Formatters
return @FORMATTERS;
}
+=head3 ValidFormatter FORMAT
+
+Returns a true value if C is a known formatter. Otherwise returns
+false.
+
+=cut
+
+sub ValidFormatter {
+ my $self = shift;
+ my $format = shift;
+ return (grep { $_ eq $format } $self->Formatters and $self->can($format))
+ ? 1 : 0;
+}
+
=head3 DefaultFormat
=cut
@@ -720,15 +738,19 @@ sub LocalizedDateTime
my %args = ( Date => 1,
Time => 1,
Timezone => '',
- DateFormat => 'date_format_full',
- TimeFormat => 'time_format_medium',
+ DateFormat => '',
+ TimeFormat => '',
AbbrDay => 1,
AbbrMonth => 1,
@_,
);
- my $date_format = $args{'DateFormat'};
- my $time_format = $args{'TimeFormat'};
+ # Require valid names for the format methods
+ my $date_format = $args{DateFormat} =~ /^\w+$/
+ ? $args{DateFormat} : 'date_format_full';
+
+ my $time_format = $args{TimeFormat} =~ /^\w+$/
+ ? $args{TimeFormat} : 'time_format_medium';
my $formatter = $self->LocaleObj;
$date_format = $formatter->$date_format;
diff --git a/rt/lib/RT/Generated.pm b/rt/lib/RT/Generated.pm
index b02a413d2..2abcf3b6e 100644
--- a/rt/lib/RT/Generated.pm
+++ b/rt/lib/RT/Generated.pm
@@ -50,7 +50,7 @@ package RT;
use warnings;
use strict;
-our $VERSION = '4.0.5';
+our $VERSION = '4.0.6';
diff --git a/rt/lib/RT/Graph/Tickets.pm b/rt/lib/RT/Graph/Tickets.pm
index 112934ea3..b839824f9 100644
--- a/rt/lib/RT/Graph/Tickets.pm
+++ b/rt/lib/RT/Graph/Tickets.pm
@@ -100,7 +100,7 @@ EOT
sub gv_escape($) {
my $value = shift;
- $value =~ s{(?=")}{\\}g;
+ $value =~ s{(?=["\\])}{\\}g;
return $value;
}
@@ -278,6 +278,14 @@ sub TicketLinks {
ShowLinkDescriptions => 0,
@_
);
+
+ my %valid_links = map { $_ => 1 }
+ qw(Members MemberOf RefersTo ReferredToBy DependsOn DependedOnBy);
+
+ # Validate our link types
+ $args{ShowLinks} = [ grep { $valid_links{$_} } @{$args{ShowLinks}} ];
+ $args{LeadingLink} = 'Members' unless $valid_links{ $args{LeadingLink} };
+
unless ( $args{'Graph'} ) {
$args{'Graph'} = GraphViz->new(
name => 'ticket_links_'. $args{'Ticket'}->id,
diff --git a/rt/lib/RT/Group.pm b/rt/lib/RT/Group.pm
index 779c02648..b367b2f96 100755
--- a/rt/lib/RT/Group.pm
+++ b/rt/lib/RT/Group.pm
@@ -1171,8 +1171,18 @@ sub CurrentUserHasRight {
}
+=head2 CurrentUserCanSee
+Always returns 1; unfortunately, for historical reasons, users have
+always been able to examine groups they have indirect access to, even if
+they do not have SeeGroup explicitly.
+=cut
+
+sub CurrentUserCanSee {
+ my $self = shift;
+ return 1;
+}
=head2 PrincipalObj
diff --git a/rt/lib/RT/Groups.pm b/rt/lib/RT/Groups.pm
index 46f1c232b..578109c4f 100755
--- a/rt/lib/RT/Groups.pm
+++ b/rt/lib/RT/Groups.pm
@@ -234,6 +234,8 @@ sub WithMember {
ALIAS2 => $members, FIELD2 => 'GroupId');
$self->Limit(ALIAS => $members, FIELD => 'MemberId', OPERATOR => '=', VALUE => $args{'PrincipalId'});
+ $self->Limit(ALIAS => $members, FIELD => 'Disabled', VALUE => 0)
+ if $args{'Recursively'};
return $members;
}
@@ -260,6 +262,12 @@ sub WithoutMember {
OPERATOR => '=',
VALUE => $args{'PrincipalId'},
);
+ $self->Limit(
+ LEFTJOIN => $members_alias,
+ ALIAS => $members_alias,
+ FIELD => 'Disabled',
+ VALUE => 0
+ ) if $args{'Recursively'};
$self->Limit(
ALIAS => $members_alias,
FIELD => 'MemberId',
diff --git a/rt/lib/RT/Handle.pm b/rt/lib/RT/Handle.pm
index bb19aa957..99d10e367 100644
--- a/rt/lib/RT/Handle.pm
+++ b/rt/lib/RT/Handle.pm
@@ -226,14 +226,12 @@ sub CheckIntegrity {
my $self = shift;
$self = new $self unless ref $self;
- do {
+ unless ($RT::Handle and $RT::Handle->dbh) {
local $@;
unless ( eval { RT::ConnectToDatabase(); 1 } ) {
return (0, 'no connection', "$@");
}
- };
-
- RT::InitLogging();
+ }
require RT::CurrentUser;
my $test_user = RT::CurrentUser->new;
@@ -748,6 +746,10 @@ sub InsertData {
my $self = shift;
my $datafile = shift;
my $root_password = shift;
+ my %args = (
+ disconnect_after => 1,
+ @_
+ );
# Slurp in stuff to insert from the datafile. Possible things to go in here:-
our (@Groups, @Users, @ACL, @Queues, @ScripActions, @ScripConditions,
@@ -1071,8 +1073,14 @@ sub InsertData {
$RT::Logger->debug("done.");
}
- my $db_type = RT->Config->Get('DatabaseType');
- $RT::Handle->Disconnect() unless $db_type eq 'SQLite';
+ # XXX: This disconnect doesn't really belong here; it's a relict from when
+ # this method was extracted from rt-setup-database. However, too much
+ # depends on it to change without significant testing. At the very least,
+ # we can provide a way to skip the side-effect.
+ if ( $args{disconnect_after} ) {
+ my $db_type = RT->Config->Get('DatabaseType');
+ $RT::Handle->Disconnect() unless $db_type eq 'SQLite';
+ }
$RT::Logger->debug("Done setting up database content.");
diff --git a/rt/lib/RT/I18N.pm b/rt/lib/RT/I18N.pm
index 971eaa1bd..cadf7cc7c 100644
--- a/rt/lib/RT/I18N.pm
+++ b/rt/lib/RT/I18N.pm
@@ -219,13 +219,6 @@ sub SetMIMEEntityToEncoding {
my $head = $entity->head;
- # convert at least MIME word encoded attachment filename
- foreach my $attr (qw(content-type.name content-disposition.filename)) {
- if ( my $name = $head->mime_attr($attr) and !$preserve_words ) {
- $head->mime_attr( $attr => DecodeMIMEWordsToUTF8($name) );
- }
- }
-
# If this is a textual entity, we'd need to preserve its original encoding
$head->replace( "X-RT-Original-Encoding" => $charset )
if $head->mime_attr('content-type.charset') or IsTextualContentType($head->mime_type);
@@ -292,7 +285,28 @@ sub DecodeMIMEWordsToEncoding {
my $to_charset = _CanonicalizeCharset(shift);
my $field = shift || '';
- my @list = $str =~ m/(.*?)=\?([^?]+)\?([QqBb])\?([^?]+)\?=([^=]*)/gcs;
+ # handle filename*=ISO-8859-1''%74%E9%73%74%2E%74%78%74, parameter value
+ # continuations, and similar syntax from RFC 2231
+ if ($field =~ /^Content-(Type|Disposition)/i) {
+ # This concatenates continued parameters and normalizes encoded params
+ # to QB encoded-words which we handle below
+ $str = MIME::Field::ParamVal->parse($str)->stringify;
+ }
+
+ # XXX TODO: use decode('MIME-Header', ...) and Encode::Alias to replace our
+ # custom MIME word decoding and charset canonicalization. We can't do this
+ # until we parse before decode, instead of the other way around.
+ my @list = $str =~ m/(.*?) # prefix
+ =\? # =?
+ ([^?]+?) # charset
+ (?:\*[^?]+)? # optional '*language'
+ \? # ?
+ ([QqBb]) # encoding
+ \? # ?
+ ([^?]+) # encoded string
+ \?= # ?=
+ ([^=]*) # trailing
+ /xgcs;
if ( @list ) {
# add everything that hasn't matched to the end of the latest
@@ -350,27 +364,6 @@ sub DecodeMIMEWordsToEncoding {
}
}
-# handle filename*=ISO-8859-1''%74%E9%73%74%2E%74%78%74, see also rfc 2231
- @list = $str =~ m/(.*?\*=)([^']*?)'([^']*?)'(\S+)(.*?)(?=(?:\*=|$))/gcs;
- if (@list) {
- $str = '';
- while (@list) {
- my ( $prefix, $charset, $language, $enc_str, $trailing ) =
- splice @list, 0, 5;
- $prefix =~ s/\*=$/=/; # remove the *
- $charset = _CanonicalizeCharset($charset);
- $enc_str =~ s/%(\w{2})/chr hex $1/eg;
- unless ( $charset eq $to_charset ) {
- Encode::from_to( $enc_str, $charset, $to_charset );
- }
- $enc_str = qq{"$enc_str"}
- if $enc_str =~ /[,;]/
- and $enc_str !~ /^".*"$/
- and (!$field || $field =~ /^(?:To$|From$|B?Cc$|Content-)/i);
- $str .= $prefix . $enc_str . $trailing;
- }
- }
-
# We might have \n without trailing whitespace, which will result in
# invalid headers.
$str =~ s/\n//g;
diff --git a/rt/lib/RT/Installer.pm b/rt/lib/RT/Installer.pm
index 3976adec6..d12abb678 100644
--- a/rt/lib/RT/Installer.pm
+++ b/rt/lib/RT/Installer.pm
@@ -252,7 +252,7 @@ sub CurrentValues {
sub ConfigFile {
require File::Spec;
- return File::Spec->catfile( $RT::EtcPath, 'RT_SiteConfig.pm' );
+ return $ENV{RT_SITE_CONFIG} || File::Spec->catfile( $RT::EtcPath, 'RT_SiteConfig.pm' );
}
sub SaveConfig {
diff --git a/rt/lib/RT/Interface/Email.pm b/rt/lib/RT/Interface/Email.pm
index b9145d63a..02a1ec0c0 100755
--- a/rt/lib/RT/Interface/Email.pm
+++ b/rt/lib/RT/Interface/Email.pm
@@ -57,6 +57,7 @@ use RT::EmailParser;
use File::Temp;
use UNIVERSAL::require;
use Mail::Mailer ();
+use Text::ParseWords qw/shellwords/;
BEGIN {
use base 'Exporter';
@@ -404,11 +405,11 @@ sub SendEmail {
if ( $mail_command eq 'sendmailpipe' ) {
my $path = RT->Config->Get('SendmailPath');
- my $args = RT->Config->Get('SendmailArguments');
+ my @args = shellwords(RT->Config->Get('SendmailArguments'));
# SetOutgoingMailFrom and bounces conflict, since they both want -f
if ( $args{'Bounce'} ) {
- $args .= ' '. RT->Config->Get('SendmailBounceArguments');
+ push @args, shellwords(RT->Config->Get('SendmailBounceArguments'));
} elsif ( RT->Config->Get('SetOutgoingMailFrom') ) {
my $OutgoingMailAddress;
@@ -425,7 +426,7 @@ sub SendEmail {
$OutgoingMailAddress ||= RT->Config->Get('OverrideOutgoingMailFrom')->{'Default'};
- $args .= " -f $OutgoingMailAddress"
+ push @args, "-f", $OutgoingMailAddress
if $OutgoingMailAddress;
}
@@ -437,32 +438,36 @@ sub SendEmail {
my $from = $TransactionObj->CreatorObj->EmailAddress;
$from =~ s/@/=/g;
$from =~ s/\s//g;
- $args .= " -f $prefix$from\@$domain";
+ push @args, "-f", "$prefix$from\@$domain";
}
eval {
# don't ignore CHLD signal to get proper exit code
local $SIG{'CHLD'} = 'DEFAULT';
- open( my $mail, '|-', "$path $args >/dev/null" )
- or die "couldn't execute program: $!";
-
# if something wrong with $mail->print we will get PIPE signal, handle it
local $SIG{'PIPE'} = sub { die "program unexpectedly closed pipe" };
+
+ require IPC::Open2;
+ my ($mail, $stdout);
+ my $pid = IPC::Open2::open2( $stdout, $mail, $path, @args )
+ or die "couldn't execute program: $!";
+
$args{'Entity'}->print($mail);
+ close $mail or die "close pipe failed: $!";
- unless ( close $mail ) {
- die "close pipe failed: $!" if $!; # system error
+ waitpid($pid, 0);
+ if ($?) {
# sendmail exit statuses mostly errors with data not software
# TODO: status parsing: core dump, exit on signal or EX_*
- my $msg = "$msgid: `$path $args` exitted with code ". ($?>>8);
+ my $msg = "$msgid: `$path @args` exited with code ". ($?>>8);
$msg = ", interrupted by signal ". ($?&127) if $?&127;
$RT::Logger->error( $msg );
die $msg;
}
};
if ( $@ ) {
- $RT::Logger->crit( "$msgid: Could not send mail with command `$path $args`: " . $@ );
+ $RT::Logger->crit( "$msgid: Could not send mail with command `$path @args`: " . $@ );
if ( $TicketObj ) {
_RecordSendEmailFailure( $TicketObj );
}
@@ -743,16 +748,19 @@ sub SendForward {
$mail->add_part( $entity );
my $from;
- my $subject = '';
- $subject = $txn->Subject if $txn;
- $subject ||= $ticket->Subject if $ticket;
+ unless (defined $mail->head->get('Subject')) {
+ my $subject = '';
+ $subject = $txn->Subject if $txn;
+ $subject ||= $ticket->Subject if $ticket;
+
+ unless ( RT->Config->Get('ForwardFromUser') ) {
+ # XXX: what if want to forward txn of other object than ticket?
+ $subject = AddSubjectTag( $subject, $ticket );
+ }
- unless ( RT->Config->Get('ForwardFromUser') ) {
- # XXX: what if want to forward txn of other object than ticket?
- $subject = AddSubjectTag( $subject, $ticket );
+ $mail->head->set( Subject => EncodeToMIME( String => "Fwd: $subject" ) );
}
- $mail->head->set( Subject => EncodeToMIME( String => "Fwd: $subject" ) );
$mail->head->set(
From => EncodeToMIME(
String => GetForwardFrom( Transaction => $txn, Ticket => $ticket )
diff --git a/rt/lib/RT/Interface/Web.pm b/rt/lib/RT/Interface/Web.pm
index 7c9d57821..814a8293f 100644
--- a/rt/lib/RT/Interface/Web.pm
+++ b/rt/lib/RT/Interface/Web.pm
@@ -158,6 +158,25 @@ sub EncodeJSON {
JSON::to_json(shift, { utf8 => 1, allow_nonref => 1 });
}
+sub _encode_surrogates {
+ my $uni = $_[0] - 0x10000;
+ return ($uni / 0x400 + 0xD800, $uni % 0x400 + 0xDC00);
+}
+
+sub EscapeJS {
+ my $ref = shift;
+ return unless defined $$ref;
+
+ $$ref = "'" . join('',
+ map {
+ chr($_) =~ /[a-zA-Z0-9]/ ? chr($_) :
+ $_ <= 255 ? sprintf("\\x%02X", $_) :
+ $_ <= 65535 ? sprintf("\\u%04X", $_) :
+ sprintf("\\u%X\\u%X", _encode_surrogates($_))
+ } unpack('U*', $$ref))
+ . "'";
+}
+
=head2 WebCanonicalizeInfo();
Different web servers set different environmental varibles. This
@@ -234,8 +253,10 @@ sub HandleRequest {
ValidateWebConfig();
DecodeARGS($ARGS);
+ local $HTML::Mason::Commands::DECODED_ARGS = $ARGS;
PreprocessTimeUpdates($ARGS);
+ InitializeMenu();
MaybeShowInstallModePage();
$HTML::Mason::Commands::m->comp( '/Elements/SetupSessionCookie', %$ARGS );
@@ -285,6 +306,8 @@ sub HandleRequest {
}
}
+ MaybeShowInterstitialCSRFPage($ARGS);
+
# now it applies not only to home page, but any dashboard that can be used as a workspace
$HTML::Mason::Commands::session{'home_refresh_interval'} = $ARGS->{'HomeRefreshInterval'}
if ( $ARGS->{'HomeRefreshInterval'} );
@@ -347,8 +370,6 @@ sub SetNextPage {
$HTML::Mason::Commands::session{'NextPage'}->{$hash} = $next;
$HTML::Mason::Commands::session{'i'}++;
-
- SendSessionCookie();
return $hash;
}
@@ -465,7 +486,6 @@ sub MaybeShowNoAuthPage {
if $m->base_comp->path eq '/NoAuth/Login.html' and _UserLoggedIn();
# If it's a noauth file, don't ask for auth.
- SendSessionCookie();
$m->comp( { base_comp => $m->request_comp }, $m->fetch_next, %$ARGS );
$m->abort;
}
@@ -492,7 +512,7 @@ sub MaybeRejectPrivateComponentRequest {
_elements | # mobile UI
Widgets |
autohandler | # requesting this directly is suspicious
- l ) # loc component
+ l (_unsafe)? ) # loc component
( $ | / ) # trailing slash or end of path
}xi
&& $path !~ m{ /RTx/Statistics/\w+/Elements/Chart }xi
@@ -526,13 +546,13 @@ sub ShowRequestedPage {
my $m = $HTML::Mason::Commands::m;
+ # Ensure that the cookie that we send is up-to-date, in case the
+ # session-id has been modified in any way
+ SendSessionCookie();
+
# precache all system level rights for the current user
$HTML::Mason::Commands::session{CurrentUser}->PrincipalObj->HasRights( Object => RT->System );
- InitializeMenu();
-
- SendSessionCookie();
-
# If the user isn't privileged, they can only see SelfService
unless ( $HTML::Mason::Commands::session{'CurrentUser'}->Privileged ) {
@@ -681,7 +701,6 @@ sub AttemptPasswordAuthentication {
InstantiateNewSession();
$HTML::Mason::Commands::session{'CurrentUser'} = $user_obj;
- SendSessionCookie();
$m->callback( %$ARGS, CallbackName => 'SuccessfulLogin', CallbackPage => '/autohandler' );
@@ -736,6 +755,7 @@ sub LoadSessionFromCookie {
sub InstantiateNewSession {
tied(%HTML::Mason::Commands::session)->delete if tied(%HTML::Mason::Commands::session);
tie %HTML::Mason::Commands::session, 'RT::Interface::Web::Session', undef;
+ SendSessionCookie();
}
sub SendSessionCookie {
@@ -817,6 +837,10 @@ sub StaticFileHeaders {
# make cache public
$HTML::Mason::Commands::r->headers_out->{'Cache-Control'} = 'max-age=259200, public';
+ # remove any cookie headers -- if it is cached publicly, it
+ # shouldn't include anyone's cookie!
+ delete $HTML::Mason::Commands::r->err_headers_out->{'Set-Cookie'};
+
# Expire things in a month.
$date->Set( Value => time + 30 * 24 * 60 * 60 );
$HTML::Mason::Commands::r->headers_out->{'Expires'} = $date->RFC2616;
@@ -828,6 +852,22 @@ sub StaticFileHeaders {
# $HTML::Mason::Commands::r->headers_out->{'Last-Modified'} = $date->RFC2616;
}
+=head2 ComponentPathIsSafe PATH
+
+Takes C 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.
+
+=cut
+
+sub ComponentPathIsSafe {
+ my $self = shift;
+ my $path = shift;
+ return $path !~ m{(?:^|/)\.};
+}
+
=head2 PathIsSafe
Takes a C<< Path => path >> and returns a boolean indicating that
@@ -1138,6 +1178,218 @@ sub ComponentRoots {
return @roots;
}
+our %is_whitelisted_component = (
+ # The RSS feed embeds an auth token in the path, but query
+ # information for the search. Because it's a straight-up read, in
+ # addition to embedding its own auth, it's fine.
+ '/NoAuth/rss/dhandler' => 1,
+);
+
+sub IsCompCSRFWhitelisted {
+ my $comp = shift;
+ my $ARGS = shift;
+
+ return 1 if $is_whitelisted_component{$comp};
+
+ my %args = %{ $ARGS };
+
+ # If the user specifies a *correct* user and pass then they are
+ # golden. This acts on the presumption that external forms may
+ # hardcode a username and password -- if a malicious attacker knew
+ # both already, CSRF is the least of your problems.
+ my $AllowLoginCSRF = not RT->Config->Get('RestrictReferrerLogin');
+ if ($AllowLoginCSRF and defined($args{user}) and defined($args{pass})) {
+ my $user_obj = RT::CurrentUser->new();
+ $user_obj->Load($args{user});
+ return 1 if $user_obj->id && $user_obj->IsPassword($args{pass});
+
+ delete $args{user};
+ delete $args{pass};
+ }
+
+ # Eliminate arguments that do not indicate an effectful request.
+ # For example, "id" is acceptable because that is how RT retrieves a
+ # record.
+ delete $args{id};
+
+ # If they have a valid results= from MaybeRedirectForResults, that's
+ # also fine.
+ delete $args{results} if $args{results}
+ and $HTML::Mason::Commands::session{"Actions"}->{$args{results}};
+
+ # The homepage refresh, which uses the Refresh header, doesn't send
+ # a referer in most browsers; whitelist the one parameter it reloads
+ # with, HomeRefreshInterval, which is safe
+ delete $args{HomeRefreshInterval};
+
+ # If there are no arguments, then it's likely to be an idempotent
+ # request, which are not susceptible to CSRF
+ return 1 if !%args;
+
+ return 0;
+}
+
+sub IsRefererCSRFWhitelisted {
+ my $referer = _NormalizeHost(shift);
+ my $base_url = _NormalizeHost(RT->Config->Get('WebBaseURL'));
+ $base_url = $base_url->host_port;
+
+ my $configs;
+ for my $config ( $base_url, RT->Config->Get('ReferrerWhitelist') ) {
+ push @$configs,$config;
+ return 1 if $referer->host_port eq $config;
+ }
+
+ return (0,$referer,$configs);
+}
+
+=head3 _NormalizeHost
+
+Takes a URI and creates a URI object that's been normalized
+to handle common problems such as localhost vs 127.0.0.1
+
+=cut
+
+sub _NormalizeHost {
+
+ my $uri= URI->new(shift);
+ $uri->host('127.0.0.1') if $uri->host eq 'localhost';
+
+ return $uri;
+
+}
+
+sub IsPossibleCSRF {
+ my $ARGS = shift;
+
+ # If first request on this session is to a REST endpoint, then
+ # whitelist the REST endpoints -- and explicitly deny non-REST
+ # endpoints. We do this because using a REST cookie in a browser
+ # would open the user to CSRF attacks to the REST endpoints.
+ my $path = $HTML::Mason::Commands::r->path_info;
+ $HTML::Mason::Commands::session{'REST'} = $path =~ m{^/+REST/\d+\.\d+(/|$)}
+ unless defined $HTML::Mason::Commands::session{'REST'};
+
+ if ($HTML::Mason::Commands::session{'REST'}) {
+ return 0 if $path =~ m{^/+REST/\d+\.\d+(/|$)};
+ my $why = < $details );
+ }
+
+ return 0 if IsCompCSRFWhitelisted(
+ $HTML::Mason::Commands::m->request_comp->path,
+ $ARGS
+ );
+
+ # if there is no Referer header then assume the worst
+ return (1,
+ "your browser did not supply a Referrer header", # loc
+ ) if !$ENV{HTTP_REFERER};
+
+ my ($whitelisted, $browser, $configs) = IsRefererCSRFWhitelisted($ENV{HTTP_REFERER});
+ return 0 if $whitelisted;
+
+ if ( @$configs > 1 ) {
+ return (1,
+ "the Referrer header supplied by your browser ([_1]) is not allowed by RT's configured hostname ([_2]) or whitelisted hosts ([_3])", # loc
+ $browser->host_port,
+ shift @$configs,
+ join(', ', @$configs) );
+ }
+
+ return (1,
+ "the Referrer header supplied by your browser ([_1]) is not allowed by RT's configured hostname ([_2])", # loc
+ $browser->host_port,
+ $configs->[0]);
+}
+
+sub ExpandCSRFToken {
+ my $ARGS = shift;
+
+ my $token = delete $ARGS->{CSRF_Token};
+ return unless $token;
+
+ my $data = $HTML::Mason::Commands::session{'CSRF'}{$token};
+ return unless $data;
+ return unless $data->{path} eq $HTML::Mason::Commands::r->path_info;
+
+ my $user = $HTML::Mason::Commands::session{'CurrentUser'}->UserObj;
+ return unless $user->ValidateAuthString( $data->{auth}, $token );
+
+ %{$ARGS} = %{$data->{args}};
+ $HTML::Mason::Commands::DECODED_ARGS = $ARGS;
+
+ # We explicitly stored file attachments with the request, but not in
+ # the session yet, as that would itself be an attack. Put them into
+ # the session now, so they'll be visible.
+ if ($data->{attach}) {
+ my $filename = $data->{attach}{filename};
+ my $mime = $data->{attach}{mime};
+ $HTML::Mason::Commands::session{'Attachments'}{$filename}
+ = $mime;
+ }
+
+ return 1;
+}
+
+sub StoreRequestToken {
+ my $ARGS = shift;
+
+ my $token = Digest::MD5::md5_hex(time . {} . $$ . rand(1024));
+ my $user = $HTML::Mason::Commands::session{'CurrentUser'}->UserObj;
+ my $data = {
+ auth => $user->GenerateAuthString( $token ),
+ path => $HTML::Mason::Commands::r->path_info,
+ args => $ARGS,
+ };
+ if ($ARGS->{Attach}) {
+ my $attachment = HTML::Mason::Commands::MakeMIMEEntity( AttachmentFieldName => 'Attach' );
+ my $file_path = delete $ARGS->{'Attach'};
+ $data->{attach} = {
+ filename => Encode::decode_utf8("$file_path"),
+ mime => $attachment,
+ };
+ }
+
+ $HTML::Mason::Commands::session{'CSRF'}->{$token} = $data;
+ $HTML::Mason::Commands::session{'i'}++;
+ return $token;
+}
+
+sub MaybeShowInterstitialCSRFPage {
+ my $ARGS = shift;
+
+ return unless RT->Config->Get('RestrictReferrer');
+
+ # Deal with the form token provided by the interstitial, which lets
+ # browsers which never set referer headers still use RT, if
+ # painfully. This blows values into ARGS
+ return if ExpandCSRFToken($ARGS);
+
+ my ($is_csrf, $msg, @loc) = IsPossibleCSRF($ARGS);
+ return if !$is_csrf;
+
+ $RT::Logger->notice("Possible CSRF: ".RT::CurrentUser->new->loc($msg, @loc));
+
+ my $token = StoreRequestToken($ARGS);
+ $HTML::Mason::Commands::m->comp(
+ '/Elements/CSRF',
+ OriginalURL => RT->Config->Get('WebPath') . $HTML::Mason::Commands::r->path_info,
+ Reason => HTML::Mason::Commands::loc( $msg, @loc ),
+ Token => $token,
+ );
+ # Calls abort, never gets here
+}
+
package HTML::Mason::Commands;
use vars qw/$r $m %session/;
@@ -1418,6 +1670,7 @@ sub CreateTicket {
my $cfid = $1;
my $cf = RT::CustomField->new( $session{'CurrentUser'} );
+ $cf->SetContextObject( $Queue );
$cf->Load($cfid);
unless ( $cf->id ) {
$RT::Logger->error( "Couldn't load custom field #" . $cfid );
@@ -2144,10 +2397,11 @@ sub ProcessTicketReminders {
if ( $args->{'update-reminders'} ) {
while ( my $reminder = $reminder_collection->Next ) {
- if ( $reminder->Status ne 'resolved' && $args->{ 'Complete-Reminder-' . $reminder->id } ) {
+ my $resolve_status = $reminder->QueueObj->Lifecycle->ReminderStatusOnResolve;
+ if ( $reminder->Status ne $resolve_status && $args->{ 'Complete-Reminder-' . $reminder->id } ) {
$Ticket->Reminders->Resolve($reminder);
}
- elsif ( $reminder->Status eq 'resolved' && !$args->{ 'Complete-Reminder-' . $reminder->id } ) {
+ elsif ( $reminder->Status eq $resolve_status && !$args->{ 'Complete-Reminder-' . $reminder->id } ) {
$Ticket->Reminders->Open($reminder);
}
@@ -2239,6 +2493,7 @@ sub ProcessObjectCustomFieldUpdates {
foreach my $cf ( keys %{ $custom_fields_to_mod{$class}{$id} } ) {
my $CustomFieldObj = RT::CustomField->new( $session{'CurrentUser'} );
+ $CustomFieldObj->SetContextObject($Object);
$CustomFieldObj->LoadById($cf);
unless ( $CustomFieldObj->id ) {
$RT::Logger->warning("Couldn't load custom field #$cf");
@@ -2851,50 +3106,71 @@ sub ScrubHTML {
=head2 _NewScrubber
-Returns a new L object. Override this if you insist on
-letting more HTML through.
+Returns a new L object.
+
+If you need to be more lax about what HTML tags and attributes are allowed,
+create C with something like the
+following:
+
+ package HTML::Mason::Commands;
+ # Let tables through
+ push @SCRUBBER_ALLOWED_TAGS, qw(TABLE THEAD TBODY TFOOT TR TD TH);
+ 1;
=cut
+our @SCRUBBER_ALLOWED_TAGS = qw(
+ A B U P BR I HR BR SMALL EM FONT SPAN STRONG SUB SUP STRIKE H1 H2 H3 H4 H5
+ H6 DIV UL OL LI DL DT DD PRE BLOCKQUOTE BDO
+);
+
+our %SCRUBBER_ALLOWED_ATTRIBUTES = (
+ # Match http, ftp and relative urls
+ # XXX: we also scrub format strings with this module then allow simple config options
+ href => qr{^(?:http:|ftp:|https:|/|__Web(?:Path|BaseURL|URL)__)}i,
+ face => 1,
+ size => 1,
+ target => 1,
+ style => qr{
+ ^(?:\s*
+ (?:(?:background-)?color: \s*
+ (?:rgb\(\s* \d+, \s* \d+, \s* \d+ \s*\) | # rgb(d,d,d)
+ \#[a-f0-9]{3,6} | # #fff or #ffffff
+ [\w\-]+ # green, light-blue, etc.
+ ) |
+ text-align: \s* \w+ |
+ font-size: \s* [\w.\-]+ |
+ font-family: \s* [\w\s"',.\-]+ |
+ font-weight: \s* [\w\-]+ |
+
+ # MS Office styles, which are probably fine. If we don't, then any
+ # associated styles in the same attribute get stripped.
+ mso-[\w\-]+?: \s* [\w\s"',.\-]+
+ )\s* ;? \s*)
+ +$ # one or more of these allowed properties from here 'till sunset
+ }ix,
+ dir => qr/^(rtl|ltr)$/i,
+ lang => qr/^\w+(-\w+)?$/,
+);
+
+our %SCRUBBER_RULES = ();
+
sub _NewScrubber {
require HTML::Scrubber;
my $scrubber = HTML::Scrubber->new();
$scrubber->default(
0,
{
- '*' => 0,
- id => 1,
- class => 1,
- # Match http, ftp and relative urls
- # XXX: we also scrub format strings with this module then allow simple config options
- href => qr{^(?:http:|ftp:|https:|/|__Web(?:Path|BaseURL|URL)__)}i,
- face => 1,
- size => 1,
- target => 1,
- style => qr{
- ^(?:\s*
- (?:(?:background-)?color: \s*
- (?:rgb\(\s* \d+, \s* \d+, \s* \d+ \s*\) | # rgb(d,d,d)
- \#[a-f0-9]{3,6} | # #fff or #ffffff
- [\w\-]+ # green, light-blue, etc.
- ) |
- text-align: \s* \w+ |
- font-size: \s* [\w.\-]+ |
- font-family: \s* [\w\s"',.\-]+ |
- font-weight: \s* [\w\-]+ |
-
- # MS Office styles, which are probably fine. If we don't, then any
- # associated styles in the same attribute get stripped.
- mso-[\w\-]+?: \s* [\w\s"',.\-]+
- )\s* ;? \s*)
- +$ # one or more of these allowed properties from here 'till sunset
- }ix,
- }
+ %SCRUBBER_ALLOWED_ATTRIBUTES,
+ '*' => 0, # require attributes be explicitly allowed
+ },
);
$scrubber->deny(qw[*]);
- $scrubber->allow(
- qw[A B U P BR I HR BR SMALL EM FONT SPAN STRONG SUB SUP STRIKE H1 H2 H3 H4 H5 H6 DIV UL OL LI DL DT DD PRE BLOCKQUOTE]
- );
+ $scrubber->allow(@SCRUBBER_ALLOWED_TAGS);
+ $scrubber->rules(%SCRUBBER_RULES);
+
+ # Scrubbing comments is vital since IE conditional comments can contain
+ # arbitrary HTML and we'd pass it right on through.
$scrubber->comment(0);
return $scrubber;
diff --git a/rt/lib/RT/Interface/Web/Handler.pm b/rt/lib/RT/Interface/Web/Handler.pm
index 69eee60f6..a740167c6 100644
--- a/rt/lib/RT/Interface/Web/Handler.pm
+++ b/rt/lib/RT/Interface/Web/Handler.pm
@@ -69,12 +69,12 @@ sub DefaultHandlerArgs { (
],
default_escape_flags => 'h',
data_dir => "$RT::MasonDataDir",
- allow_globals => [qw(%session)],
+ allow_globals => [qw(%session $DECODED_ARGS)],
# Turn off static source if we're in developer mode.
static_source => (RT->Config->Get('DevelMode') ? '0' : '1'),
use_object_files => (RT->Config->Get('DevelMode') ? '0' : '1'),
autoflush => 0,
- error_format => (RT->Config->Get('DevelMode') ? 'html': 'brief'),
+ error_format => (RT->Config->Get('DevelMode') ? 'html': 'rt_error'),
request_class => 'RT::Interface::Web::Request',
named_component_subs => $INC{'Devel/Cover.pm'} ? 1 : 0,
) };
@@ -116,6 +116,7 @@ sub NewHandler {
$handler->interp->set_escape( h => \&RT::Interface::Web::EscapeUTF8 );
$handler->interp->set_escape( u => \&RT::Interface::Web::EscapeURI );
+ $handler->interp->set_escape( j => \&RT::Interface::Web::EscapeJS );
return($handler);
}
@@ -202,6 +203,13 @@ sub CleanupRequest {
}
+sub HTML::Mason::Exception::as_rt_error {
+ my ($self) = @_;
+ $RT::Logger->error( $self->full_message );
+ return "An internal RT error has occurred. Your administrator can find more details in RT's log files.";
+}
+
+
# PSGI App
use RT::Interface::Web::Handler;
diff --git a/rt/lib/RT/Interface/Web/QueryBuilder/Tree.pm b/rt/lib/RT/Interface/Web/QueryBuilder/Tree.pm
index e2ec1e58d..2cfc88998 100755
--- a/rt/lib/RT/Interface/Web/QueryBuilder/Tree.pm
+++ b/rt/lib/RT/Interface/Web/QueryBuilder/Tree.pm
@@ -92,8 +92,8 @@ sub TraversePrePost {
=head2 GetReferencedQueues
-Returns a hash reference with keys each queue name referenced in a clause in
-the key (even if it's "Queue != 'Foo'"), and values all 1.
+Returns a hash reference; each queue referenced with an '=' operation
+will appear as a key whose value is 1.
=cut
@@ -110,10 +110,12 @@ sub GetReferencedQueues {
return unless $node->isLeaf;
my $clause = $node->getNodeValue();
+ return unless $clause->{Key} eq 'Queue';
+ return unless $clause->{Op} eq '=';
- if ( $clause->{Key} eq 'Queue' ) {
- $queues->{ $clause->{Value} } = 1;
- };
+ my $value = $clause->{Value};
+ $value =~ s/\\(.)/$1/g if $value =~ s/^'(.*)'$/$1/;
+ $queues->{ $value } = 1;
}
);
@@ -275,7 +277,7 @@ sub ParseSQL {
$value = "'$value'";
}
- if ($key =~ s/(['\\])/\\$1/g or $key =~ /\s/) {
+ if ($key =~ s/(['\\])/\\$1/g or $key =~ /[^{}\w\.]/) {
$key = "'$key'";
}
diff --git a/rt/lib/RT/Lifecycle.pm b/rt/lib/RT/Lifecycle.pm
index edb179569..056599edb 100644
--- a/rt/lib/RT/Lifecycle.pm
+++ b/rt/lib/RT/Lifecycle.pm
@@ -367,6 +367,28 @@ sub DefaultOnMerge {
return $self->DefaultStatus('on_merge');
}
+=head3 ReminderStatusOnOpen
+
+Returns the status that should be used when reminders are opened.
+
+=cut
+
+sub ReminderStatusOnOpen {
+ my $self = shift;
+ return $self->DefaultStatus('reminder_on_open') || 'open';
+}
+
+=head3 ReminderStatusOnResolve
+
+Returns the status that should be used when reminders are resolved.
+
+=cut
+
+sub ReminderStatusOnResolve {
+ my $self = shift;
+ return $self->DefaultStatus('reminder_on_resolve') || 'resolved';
+}
+
=head2 Transitions, rights, labels and actions.
=head3 Transitions
diff --git a/rt/lib/RT/Links.pm b/rt/lib/RT/Links.pm
index 0d8ed2f11..ccc72d749 100644
--- a/rt/lib/RT/Links.pm
+++ b/rt/lib/RT/Links.pm
@@ -91,15 +91,17 @@ sub Limit {
if ( ($args{'FIELD'} eq 'Target') or
($args{'FIELD'} eq 'LocalTarget') ) {
- $self->OrderBy (ALIAS => 'main',
- FIELD => 'Base',
- ORDER => 'ASC');
+ $self->OrderByCols(
+ { ALIAS => 'main', FIELD => 'LocalBase', ORDER => 'ASC' },
+ { ALIAS => 'main', FIELD => 'Base', ORDER => 'ASC' },
+ );
}
elsif ( ($args{'FIELD'} eq 'Base') or
($args{'FIELD'} eq 'LocalBase') ) {
- $self->OrderBy (ALIAS => 'main',
- FIELD => 'Target',
- ORDER => 'ASC');
+ $self->OrderByCols(
+ { ALIAS => 'main', FIELD => 'LocalTarget', ORDER => 'ASC' },
+ { ALIAS => 'main', FIELD => 'Target', ORDER => 'ASC' },
+ );
}
diff --git a/rt/lib/RT/ObjectCustomField.pm b/rt/lib/RT/ObjectCustomField.pm
index 0b815aef3..61bc35532 100644
--- a/rt/lib/RT/ObjectCustomField.pm
+++ b/rt/lib/RT/ObjectCustomField.pm
@@ -137,7 +137,19 @@ Returns the CustomField Object which has the id returned by CustomField
sub CustomFieldObj {
my $self = shift;
my $id = shift || $self->CustomField;
+
+ # To find out the proper context object to load the CF with, we need
+ # data from the CF -- namely, the record class. Go find that as the
+ # system user first.
+ my $system_CF = RT::CustomField->new( RT->SystemUser );
+ $system_CF->Load( $id );
+ my $class = $system_CF->RecordClassFromLookupType;
+
+ my $obj = $class->new( $self->CurrentUser );
+ $obj->Load( $self->ObjectId );
+
my $CF = RT::CustomField->new( $self->CurrentUser );
+ $CF->SetContextObject( $obj );
$CF->Load( $id );
return $CF;
}
diff --git a/rt/lib/RT/ObjectCustomFieldValue.pm b/rt/lib/RT/ObjectCustomFieldValue.pm
index 0fd9d735c..98714a048 100644
--- a/rt/lib/RT/ObjectCustomFieldValue.pm
+++ b/rt/lib/RT/ObjectCustomFieldValue.pm
@@ -251,6 +251,8 @@ my $re_ip_serialized = qr/$re_ip_sunit(?:\.$re_ip_sunit){3}/;
sub Content {
my $self = shift;
+ return undef unless $self->CustomFieldObj->CurrentUserHasRight('SeeCustomField');
+
my $content = $self->_Value('Content');
if ( $self->CustomFieldObj->Type eq 'IPAddress'
|| $self->CustomFieldObj->Type eq 'IPAddressRange' )
@@ -364,11 +366,11 @@ sub _FillInTemplateURL {
# special case, whole value should be an URL
if ( $url =~ /^__CustomField__/ ) {
my $value = $self->Content;
- # protect from javascript: URLs
- if ( $value =~ /^\s*javascript:/i ) {
+ # protect from potentially malicious URLs
+ if ( $value =~ /^\s*(?:javascript|data):/i ) {
my $object = $self->Object;
$RT::Logger->error(
- "Dangerouse value with JavaScript in custom field '". $self->CustomFieldObj->Name ."'"
+ "Potentially dangerous URL type in custom field '". $self->CustomFieldObj->Name ."'"
." on ". ref($object) ." #". $object->id
);
return undef;
diff --git a/rt/lib/RT/Queue.pm b/rt/lib/RT/Queue.pm
index 3cb87c4be..406df9214 100755
--- a/rt/lib/RT/Queue.pm
+++ b/rt/lib/RT/Queue.pm
@@ -692,6 +692,7 @@ sub TicketTransactionCustomFields {
my $cfs = RT::CustomFields->new( $self->CurrentUser );
if ( $self->CurrentUserHasRight('SeeQueue') ) {
+ $cfs->SetContextObject( $self );
$cfs->LimitToGlobalOrObjectId( $self->Id );
$cfs->LimitToLookupType( 'RT::Queue-RT::Ticket-RT::Transaction' );
$cfs->ApplySortOrder;
@@ -1249,6 +1250,17 @@ sub CurrentUserHasRight {
}
+=head2 CurrentUserCanSee
+
+Returns true if the current user can see the queue, using SeeQueue
+
+=cut
+
+sub CurrentUserCanSee {
+ my $self = shift;
+
+ return $self->CurrentUserHasRight('SeeQueue');
+}
=head2 HasRight
diff --git a/rt/lib/RT/Reminders.pm b/rt/lib/RT/Reminders.pm
index e7c28a8e1..2b663256a 100644
--- a/rt/lib/RT/Reminders.pm
+++ b/rt/lib/RT/Reminders.pm
@@ -137,7 +137,8 @@ sub Open {
my $self = shift;
my $reminder = shift;
- my ( $status, $msg ) = $reminder->SetStatus('open');
+ my ( $status, $msg ) =
+ $reminder->SetStatus( $reminder->QueueObj->Lifecycle->ReminderStatusOnOpen );
$self->TicketObj->_NewTransaction(
Type => 'OpenReminder',
Field => 'RT::Ticket',
@@ -149,7 +150,8 @@ sub Open {
sub Resolve {
my $self = shift;
my $reminder = shift;
- my ( $status, $msg ) = $reminder->SetStatus('resolved');
+ my ( $status, $msg ) =
+ $reminder->SetStatus( $reminder->QueueObj->Lifecycle->ReminderStatusOnResolve );
$self->TicketObj->_NewTransaction(
Type => 'ResolveReminder',
Field => 'RT::Ticket',
diff --git a/rt/lib/RT/Report/Tickets.pm b/rt/lib/RT/Report/Tickets.pm
index af6e4ed15..de40dbdd4 100644
--- a/rt/lib/RT/Report/Tickets.pm
+++ b/rt/lib/RT/Report/Tickets.pm
@@ -89,13 +89,7 @@ sub Groupings {
foreach my $id (keys %$queues) {
my $queue = RT::Queue->new( $self->CurrentUser );
$queue->Load($id);
- unless ($queue->id) {
- # XXX TODO: This ancient code dates from a former developer
- # we have no idea what it means or why cfqueues are so encoded.
- $id =~ s/^.'*(.*).'*$/$1/;
- $queue->Load($id);
- }
- $CustomFields->LimitToQueue($queue->Id);
+ $CustomFields->LimitToQueue($queue->Id) if $queue->Id;
}
$CustomFields->LimitToGlobal;
while ( my $CustomField = $CustomFields->Next ) {
diff --git a/rt/lib/RT/Report/Tickets/Entry.pm b/rt/lib/RT/Report/Tickets/Entry.pm
index f3eeb4d69..87754c47d 100644
--- a/rt/lib/RT/Report/Tickets/Entry.pm
+++ b/rt/lib/RT/Report/Tickets/Entry.pm
@@ -83,6 +83,10 @@ sub LabelValue {
return $value;
}
+sub ObjectType {
+ return 'RT::Ticket';
+}
+
RT::Base->_ImportOverlays();
1;
diff --git a/rt/lib/RT/Scrip.pm b/rt/lib/RT/Scrip.pm
index d513e0aa0..950661624 100755
--- a/rt/lib/RT/Scrip.pm
+++ b/rt/lib/RT/Scrip.pm
@@ -514,13 +514,35 @@ sub _Set {
}
- if (length($args{Value})) {
+ if (exists $args{Value}) {
if ($args{Field} eq 'CustomIsApplicableCode' || $args{Field} eq 'CustomPrepareCode' || $args{Field} eq 'CustomCommitCode') {
unless ( $self->CurrentUser->HasRight( Object => $RT::System,
Right => 'ExecuteCode' ) ) {
return ( 0, $self->loc('Permission Denied') );
}
}
+ elsif ($args{Field} eq 'Queue') {
+ if ($args{Value}) {
+ # moving to another queue
+ my $queue = RT::Queue->new( $self->CurrentUser );
+ $queue->Load($args{Value});
+ unless ($queue->Id and $queue->CurrentUserHasRight('ModifyScrips')) {
+ return ( 0, $self->loc('Permission Denied') );
+ }
+ } else {
+ # moving to global
+ unless ($self->CurrentUser->HasRight( Object => RT->System, Right => 'ModifyScrips' )) {
+ return ( 0, $self->loc('Permission Denied') );
+ }
+ }
+ }
+ elsif ($args{Field} eq 'Template') {
+ my $template = RT::Template->new( $self->CurrentUser );
+ $template->Load($args{Value});
+ unless ($template->Id and $template->CurrentUserCanRead) {
+ return ( 0, $self->loc('Permission Denied') );
+ }
+ }
}
return $self->__Set(@_);
diff --git a/rt/lib/RT/SearchBuilder.pm b/rt/lib/RT/SearchBuilder.pm
index 02d4c5058..3e9855110 100644
--- a/rt/lib/RT/SearchBuilder.pm
+++ b/rt/lib/RT/SearchBuilder.pm
@@ -105,10 +105,14 @@ sub JoinTransactions {
TABLE2 => 'Transactions',
FIELD2 => 'ObjectId',
);
+
+ my $item = $self->NewItem;
+ my $object_type = $item->can('ObjectType') ? $item->ObjectType : ref $item;
+
$self->RT::SearchBuilder::Limit(
LEFTJOIN => $alias,
FIELD => 'ObjectType',
- VALUE => ref $self->NewItem,
+ VALUE => $object_type,
);
$self->{'_sql_aliases'}{'transactions'} = $alias
unless $args{'New'};
@@ -127,6 +131,19 @@ sub OrderByCols {
return $self->SUPER::OrderByCols( @sort );
}
+# If we're setting RowsPerPage or FirstRow, ensure we get a natural number or undef.
+sub RowsPerPage {
+ my $self = shift;
+ return if @_ and defined $_[0] and $_[0] =~ /\D/;
+ return $self->SUPER::RowsPerPage(@_);
+}
+
+sub FirstRow {
+ my $self = shift;
+ return if @_ and defined $_[0] and $_[0] =~ /\D/;
+ return $self->SUPER::FirstRow(@_);
+}
+
=head2 LimitToEnabled
Only find items that haven't been disabled
diff --git a/rt/lib/RT/Shredder.pm b/rt/lib/RT/Shredder.pm
index 10d353677..40c73b36d 100644
--- a/rt/lib/RT/Shredder.pm
+++ b/rt/lib/RT/Shredder.pm
@@ -351,6 +351,8 @@ sub CastObjectsToRecords
} elsif ( UNIVERSAL::isa( $targets, 'SCALAR' ) || !ref $targets ) {
$targets = $$targets if ref $targets;
my ($class, $id) = split /-/, $targets;
+ RT::Shredder::Exception->throw( "Unsupported class $class" )
+ unless $class =~ /^\w+(::\w+)*$/;
$class = 'RT::'. $class unless $class =~ /^RTx?::/i;
eval "require $class";
die "Couldn't load '$class' module" if $@;
diff --git a/rt/lib/RT/Shredder/Plugin.pm b/rt/lib/RT/Shredder/Plugin.pm
index e70d207ac..ad9af6ac6 100644
--- a/rt/lib/RT/Shredder/Plugin.pm
+++ b/rt/lib/RT/Shredder/Plugin.pm
@@ -167,6 +167,7 @@ sub LoadByName
{
my $self = shift;
my $name = shift or return (0, "Name not specified");
+ $name =~ /^\w+(::\w+)*$/ or return (0, "Invalid plugin name");
local $@;
my $plugin = "RT::Shredder::Plugin::$name";
diff --git a/rt/lib/RT/Shredder/Queue.pm b/rt/lib/RT/Shredder/Queue.pm
index c2ec44538..2c0d068fb 100644
--- a/rt/lib/RT/Shredder/Queue.pm
+++ b/rt/lib/RT/Shredder/Queue.pm
@@ -91,6 +91,7 @@ sub __DependsOn
# Custom Fields
$objs = RT::CustomFields->new( $self->CurrentUser );
+ $objs->SetContextObject( $self );
$objs->LimitToQueue( $self->id );
push( @$list, $objs );
diff --git a/rt/lib/RT/Template.pm b/rt/lib/RT/Template.pm
index 158547a0e..117cc3f1c 100755
--- a/rt/lib/RT/Template.pm
+++ b/rt/lib/RT/Template.pm
@@ -96,10 +96,34 @@ sub _Accessible {
sub _Set {
my $self = shift;
+ my %args = (
+ Field => undef,
+ Value => undef,
+ @_,
+ );
unless ( $self->CurrentUserHasQueueRight('ModifyTemplate') ) {
return ( 0, $self->loc('Permission Denied') );
}
+
+ if (exists $args{Value}) {
+ if ($args{Field} eq 'Queue') {
+ if ($args{Value}) {
+ # moving to another queue
+ my $queue = RT::Queue->new( $self->CurrentUser );
+ $queue->Load($args{Value});
+ unless ($queue->Id and $queue->CurrentUserHasRight('ModifyTemplate')) {
+ return ( 0, $self->loc('Permission Denied') );
+ }
+ } else {
+ # moving to global
+ unless ($self->CurrentUser->HasRight( Object => RT->System, Right => 'ModifyTemplate' )) {
+ return ( 0, $self->loc('Permission Denied') );
+ }
+ }
+ }
+ }
+
return $self->SUPER::_Set( @_ );
}
diff --git a/rt/lib/RT/Test.pm b/rt/lib/RT/Test.pm
index 0d6da1b9e..7d69dd60d 100644
--- a/rt/lib/RT/Test.pm
+++ b/rt/lib/RT/Test.pm
@@ -409,7 +409,11 @@ sub bootstrap_db {
$args{$forceopt}=1;
}
- return if $args{nodb};
+ # Short-circuit the rest of ourselves if we don't want a db
+ if ($args{nodb}) {
+ __drop_database();
+ return;
+ }
my $db_type = RT->Config->Get('DatabaseType');
__create_database();
@@ -556,6 +560,13 @@ sub __drop_database {
RT::Handle->SystemDSN,
$ENV{RT_DBA_USER}, $ENV{RT_DBA_PASSWORD}
);
+
+ # We ignore errors intentionally by not checking the return value of
+ # DropDatabase below, so let's also suppress DBI's printing of errors when
+ # we overzealously drop.
+ local $dbh->{PrintError} = 0;
+ local $dbh->{PrintWarn} = 0;
+
RT::Handle->DropDatabase( $dbh );
$dbh->disconnect if $my_dbh;
}
@@ -1276,8 +1287,10 @@ sub started_ok {
require RT::Test::Web;
- if ($rttest_opt{nodb}) {
- die "you are trying to use a test web server without db, try use noinitialdata => 1 instead";
+ if ($rttest_opt{nodb} and not $rttest_opt{server_ok}) {
+ die "You are trying to use a test web server without a database. "
+ ."You may want noinitialdata => 1 instead. "
+ ."Pass server_ok => 1 if you know what you're doing.";
}
@@ -1298,11 +1311,31 @@ sub test_app {
my $self = shift;
my %server_opt = @_;
- require RT::Interface::Web::Handler;
- my $app = RT::Interface::Web::Handler->PSGIApp;
+ my $app;
+
+ my $warnings = "";
+ open( my $warn_fh, ">", \$warnings );
+ local *STDERR = $warn_fh;
+
+ if ($server_opt{variant} and $server_opt{variant} eq 'rt-server') {
+ $app = do {
+ my $file = "$RT::SbinPath/rt-server";
+ my $psgi = do $file;
+ unless ($psgi) {
+ die "Couldn't parse $file: $@" if $@;
+ die "Couldn't do $file: $!" unless defined $psgi;
+ die "Couldn't run $file" unless $psgi;
+ }
+ $psgi;
+ };
+ } else {
+ require RT::Interface::Web::Handler;
+ $app = RT::Interface::Web::Handler->PSGIApp;
+ }
require Plack::Middleware::Test::StashWarnings;
- $app = Plack::Middleware::Test::StashWarnings->wrap($app);
+ my $stashwarnings = Plack::Middleware::Test::StashWarnings->new;
+ $app = $stashwarnings->wrap($app);
if ($server_opt{basic_auth}) {
require Plack::Middleware::Auth::Basic;
@@ -1314,6 +1347,10 @@ sub test_app {
}
);
}
+
+ close $warn_fh;
+ $stashwarnings->add_warning( $warnings ) if $warnings;
+
return $app;
}
@@ -1346,7 +1383,8 @@ sub start_plack_server {
my $Tester = Test::Builder->new;
$Tester->ok(1, "started plack server ok");
- __reconnect_rt();
+ __reconnect_rt()
+ unless $rttest_opt{nodb};
return ("http://localhost:$port", RT::Test::Web->new);
}
diff --git a/rt/lib/RT/Test/Web.pm b/rt/lib/RT/Test/Web.pm
index 28ca3b844..c2d9ac314 100644
--- a/rt/lib/RT/Test/Web.pm
+++ b/rt/lib/RT/Test/Web.pm
@@ -52,15 +52,19 @@ use strict;
use warnings;
use base qw(Test::WWW::Mechanize);
+use Scalar::Util qw(weaken);
-require RT::Test;
+BEGIN { require RT::Test; }
require Test::More;
+my $instance;
+
sub new {
my ($class, @args) = @_;
push @args, app => $RT::Test::TEST_APP if $RT::Test::TEST_APP;
- my $self = $class->SUPER::new(@args);
+ my $self = $instance = $class->SUPER::new(@args);
+ weaken $instance;
$self->cookie_jar(HTTP::Cookies->new);
return $self;
@@ -100,6 +104,7 @@ sub login {
Test::More::diag("error: page has no Logout");
return 0;
}
+ RT::Interface::Web::EscapeUTF8(\$user);
unless ( $self->content =~ m{\Q$user\E}i ) {
Test::More::diag("Page has no user name");
return 0;
@@ -370,4 +375,10 @@ sub DESTROY {
}
}
+END {
+ return unless $instance;
+ return if RT::Test->builder->{Original_Pid} != $$;
+ $instance->no_warnings_ok if !$RT::Test::Web::DESTROY++;
+}
+
1;
diff --git a/rt/lib/RT/Ticket.pm b/rt/lib/RT/Ticket.pm
index 27bdc48ee..de8a396da 100755
--- a/rt/lib/RT/Ticket.pm
+++ b/rt/lib/RT/Ticket.pm
@@ -2438,7 +2438,7 @@ sub _Links {
my $links = $self->{ $cache_key }
= RT::Links->new( $self->CurrentUser );
unless ( $self->CurrentUserHasRight('ShowTicket') ) {
- $links->Limit( FIELD => 'id', VALUE => 0 );
+ $links->Limit( FIELD => 'id', VALUE => 0, SUBCLAUSE => 'acl' );
return $links;
}
@@ -3594,6 +3594,16 @@ sub CurrentUserHasRight {
}
+=head2 CurrentUserCanSee
+
+Returns true if the current user can see the ticket, using ShowTicket
+
+=cut
+
+sub CurrentUserCanSee {
+ my $self = shift;
+ return $self->CurrentUserHasRight('ShowTicket');
+}
=head2 HasRight
@@ -3706,7 +3716,9 @@ sub Transactions {
sub TransactionCustomFields {
my $self = shift;
- return $self->QueueObj->TicketTransactionCustomFields;
+ my $cfs = $self->QueueObj->TicketTransactionCustomFields;
+ $cfs->SetContextObject( $self );
+ return $cfs;
}
diff --git a/rt/lib/RT/Tickets.pm b/rt/lib/RT/Tickets.pm
index db54b525b..4e2415b37 100755
--- a/rt/lib/RT/Tickets.pm
+++ b/rt/lib/RT/Tickets.pm
@@ -1132,6 +1132,12 @@ sub _GroupMembersJoin {
FIELD2 => 'GroupId',
ENTRYAGGREGATOR => 'AND',
);
+ $self->SUPER::Limit(
+ $args{'Left'} ? (LEFTJOIN => $alias) : (),
+ ALIAS => $alias,
+ FIELD => 'Disabled',
+ VALUE => 0,
+ );
$self->{'_sql_group_members_aliases'}{ $args{'GroupsAlias'} } = $alias
unless $args{'New'};
@@ -1296,6 +1302,12 @@ sub _WatcherMembershipLimit {
FIELD2 => 'id'
);
+ $self->Limit(
+ ALIAS => $groupmembers,
+ FIELD => 'Disabled',
+ VALUE => 0,
+ );
+
$self->Join(
ALIAS1 => $memberships,
FIELD1 => 'MemberId',
@@ -1303,6 +1315,13 @@ sub _WatcherMembershipLimit {
FIELD2 => 'id'
);
+ $self->Limit(
+ ALIAS => $memberships,
+ FIELD => 'Disabled',
+ VALUE => 0,
+ );
+
+
$self->_CloseParen;
}
@@ -1639,11 +1658,8 @@ sub _CustomFieldLimit {
$self->_CloseParen;
}
else {
- my $cf = RT::CustomField->new( $self->CurrentUser );
- $cf->Load($field);
-
# need special treatment for Date
- if ( $cf->Type eq 'DateTime' && $op eq '=' ) {
+ if ( $cf and $cf->Type eq 'DateTime' and $op eq '=' ) {
if ( $value =~ /:/ ) {
# there is time speccified.
@@ -3647,7 +3663,11 @@ sub _RestrictionsToClauses {
# here is where we store extra data, say if it's a keyword or
# something. (I.e. "TYPE SPECIFIC STUFF")
- push @{ $clause{$realfield} }, $data;
+ if (lc $ea eq 'none') {
+ $clause{$realfield} = [ $data ];
+ } else {
+ push @{ $clause{$realfield} }, $data;
+ }
}
return \%clause;
}
diff --git a/rt/lib/RT/Transaction.pm b/rt/lib/RT/Transaction.pm
index d0e64568b..1f1bab15a 100755
--- a/rt/lib/RT/Transaction.pm
+++ b/rt/lib/RT/Transaction.pm
@@ -512,7 +512,7 @@ sub Attachments {
$self->{'attachments'} = RT::Attachments->new( $self->CurrentUser );
unless ( $self->CurrentUserCanSee ) {
- $self->{'attachments'}->Limit(FIELD => 'id', VALUE => '0');
+ $self->{'attachments'}->Limit(FIELD => 'id', VALUE => '0', SUBCLAUSE => 'acl');
return $self->{'attachments'};
}
@@ -714,6 +714,7 @@ sub BriefDescription {
if ( $self->Field ) {
my $cf = RT::CustomField->new( $self->CurrentUser );
+ $cf->SetContextObject( $self->Object );
$cf->Load( $self->Field );
$field = $cf->Name();
$field = $self->loc('a custom field') if !defined($field);
@@ -1072,14 +1073,8 @@ sub CurrentUserCanSee {
$cf->Load( $cf_id );
return 0 unless $cf->CurrentUserHasRight('SeeCustomField');
}
- #if they ain't got rights to see, don't let em
- elsif ( $self->__Value('ObjectType') eq "RT::Ticket" ) {
- unless ( $self->CurrentUserHasRight('ShowTicket') ) {
- return 0;
- }
- }
-
- return 1;
+ # Defer to the object in question
+ return $self->Object->CurrentUserCanSee("Transaction");
}
@@ -1103,7 +1098,7 @@ sub OldValue {
return $Object->Content;
}
else {
- return $self->__Value('OldValue');
+ return $self->_Value('OldValue');
}
}
@@ -1117,7 +1112,7 @@ sub NewValue {
return $Object->Content;
}
else {
- return $self->__Value('NewValue');
+ return $self->_Value('NewValue');
}
}
@@ -1207,6 +1202,7 @@ sub CustomFieldValues {
# do we want to cover this situation somehow here?
unless ( defined $field && $field =~ /^\d+$/o ) {
my $CFs = RT::CustomFields->new( $self->CurrentUser );
+ $CFs->SetContextObject( $self->Object );
$CFs->Limit( FIELD => 'Name', VALUE => $field );
$CFs->LimitToLookupType($self->CustomFieldLookupType);
$CFs->LimitToGlobalOrObjectId($self->Object->QueueObj->id);
diff --git a/rt/lib/RT/URI.pm b/rt/lib/RT/URI.pm
index 4af1cb0da..fce04598a 100644
--- a/rt/lib/RT/URI.pm
+++ b/rt/lib/RT/URI.pm
@@ -130,7 +130,7 @@ sub FromURI {
# Special case: integers passed in as URIs must be ticket ids
if ($uri =~ /^(\d+)$/) {
$scheme = "fsck.com-rt";
- } elsif ($uri =~ /^((?:\w|\.|-)+?):/) {
+ } elsif ($uri =~ /^((?!javascript|data)(?:\w|\.|-)+?):/i) {
$scheme = $1;
}
else {
diff --git a/rt/lib/RT/URI/fsck_com_article.pm b/rt/lib/RT/URI/fsck_com_article.pm
index 503434a1c..0c09b7c3c 100644
--- a/rt/lib/RT/URI/fsck_com_article.pm
+++ b/rt/lib/RT/URI/fsck_com_article.pm
@@ -188,7 +188,7 @@ Otherwise, return its URI
sub HREF {
my $self = shift;
if ($self->IsLocal && $self->Object) {
- return ( RT->Config->Get('WebURL') . "/Articles/Article/Display.html?id=".$self->Object->Id);
+ return ( RT->Config->Get('WebURL') . "Articles/Article/Display.html?id=".$self->Object->Id);
}
else {
return ($self->URI);
diff --git a/rt/lib/RT/User.pm b/rt/lib/RT/User.pm
index 2a14cd154..9b4a82683 100755
--- a/rt/lib/RT/User.pm
+++ b/rt/lib/RT/User.pm
@@ -1206,6 +1206,37 @@ sub HasRight {
return $self->PrincipalObj->HasRight(@_);
}
+=head2 CurrentUserCanSee [FIELD]
+
+Returns true if the current user can see the user, based on if it is
+public, ourself, or we have AdminUsers
+
+=cut
+
+sub CurrentUserCanSee {
+ my $self = shift;
+ my ($what) = @_;
+
+ # If it's public, fine. Note that $what may be "transaction", which
+ # doesn't have an Accessible value, and thus falls through below.
+ if ( $self->_Accessible( $what, 'public' ) ) {
+ return 1;
+ }
+
+ # Users can see their own properties
+ elsif ( defined($self->Id) && $self->CurrentUser->Id == $self->Id ) {
+ return 1;
+ }
+
+ # If the user has the admin users right, that's also enough
+ elsif ( $self->CurrentUser->HasRight( Right => 'AdminUsers', Object => $RT::System) ) {
+ return 1;
+ }
+ else {
+ return 0;
+ }
+}
+
=head2 CurrentUserCanModify RIGHT
If the user has rights for this object, either because
@@ -1334,12 +1365,13 @@ sub Stylesheet {
my $style = RT->Config->Get('WebDefaultStylesheet', $self->CurrentUser);
+ if (RT::Interface::Web->ComponentPathIsSafe($style)) {
+ my @css_paths = map { $_ . '/NoAuth/css' } RT::Interface::Web->ComponentRoots;
- my @css_paths = map { $_ . '/NoAuth/css' } RT::Interface::Web->ComponentRoots;
-
- for my $css_path (@css_paths) {
- if (-d "$css_path/$style") {
- return $style
+ for my $css_path (@css_paths) {
+ if (-d "$css_path/$style") {
+ return $style
+ }
}
}
@@ -1409,6 +1441,12 @@ sub WatchedQueues {
FIELD => 'MemberId',
VALUE => $self->PrincipalId,
);
+ $watched_queues->Limit(
+ ALIAS => $queues_alias,
+ FIELD => 'Disabled',
+ VALUE => 0,
+ );
+
$RT::Logger->debug("WatchedQueues got " . $watched_queues->Count . " queues");
@@ -1447,7 +1485,9 @@ sub _Set {
if ( $ret == 0 ) { return ( 0, $msg ); }
if ( $args{'RecordTransaction'} == 1 ) {
-
+ if ($args{'Field'} eq "Password") {
+ $args{'Value'} = $Old = '********';
+ }
my ( $Trans, $Msg, $TransObj ) = $self->_NewTransaction(
Type => $args{'TransactionType'},
Field => $args{'Field'},
@@ -1473,25 +1513,9 @@ sub _Value {
my $self = shift;
my $field = shift;
- #if the field is public, return it.
- if ( $self->_Accessible( $field, 'public' ) ) {
- return ( $self->SUPER::_Value($field) );
-
- }
-
- #If the user wants to see their own values, let them
- # TODO figure ouyt a better way to deal with this
- elsif ( defined($self->Id) && $self->CurrentUser->Id == $self->Id ) {
- return ( $self->SUPER::_Value($field) );
- }
-
- #If the user has the admin users right, return the field
- elsif ( $self->CurrentUser->HasRight(Right =>'AdminUsers', Object => $RT::System) ) {
- return ( $self->SUPER::_Value($field) );
- } else {
- return (undef);
- }
-
+ # Defer to the abstraction above to know if the field can be read
+ return $self->SUPER::_Value($field) if $self->CurrentUserCanSee($field);
+ return undef;
}
=head2 FriendlyName
diff --git a/rt/lib/RT/Users.pm b/rt/lib/RT/Users.pm
index f3b1b5cef..2784fc757 100755
--- a/rt/lib/RT/Users.pm
+++ b/rt/lib/RT/Users.pm
@@ -188,6 +188,9 @@ sub MemberOfGroup {
FIELD1 => 'id',
ALIAS2 => $groupalias,
FIELD2 => 'MemberId' );
+ $self->Limit( ALIAS => $groupalias,
+ FIELD => 'Disabled',
+ VALUE => 0 );
$self->Limit( ALIAS => "$groupalias",
FIELD => 'GroupId',
@@ -266,6 +269,11 @@ sub _JoinGroupMembers
ALIAS2 => $principals,
FIELD2 => 'id'
);
+ $self->Limit(
+ ALIAS => $group_members,
+ FIELD => 'Disabled',
+ VALUE => 0,
+ ) if $args{'IncludeSubgroupMembers'};
return $group_members;
}
diff --git a/rt/sbin/rt-server.fcgi.in b/rt/sbin/rt-server.fcgi.in
index b438202dd..45c377088 100644
--- a/rt/sbin/rt-server.fcgi.in
+++ b/rt/sbin/rt-server.fcgi.in
@@ -91,6 +91,7 @@ if (grep { m/help/ } @ARGV) {
require RT;
RT->LoadConfig();
+RT->InitLogging();
require Module::Refresh if RT->Config->Get('DevelMode');
require RT::Handle;
diff --git a/rt/sbin/rt-server.in b/rt/sbin/rt-server.in
index b438202dd..45c377088 100644
--- a/rt/sbin/rt-server.in
+++ b/rt/sbin/rt-server.in
@@ -91,6 +91,7 @@ if (grep { m/help/ } @ARGV) {
require RT;
RT->LoadConfig();
+RT->InitLogging();
require Module::Refresh if RT->Config->Get('DevelMode');
require RT::Handle;
diff --git a/rt/sbin/rt-shredder.in b/rt/sbin/rt-shredder.in
index c52116f82..c0655dbe1 100755
--- a/rt/sbin/rt-shredder.in
+++ b/rt/sbin/rt-shredder.in
@@ -74,7 +74,7 @@ should wipeout.
=head2 --sqldump
-Outputs INSERT queiries into file. This dump can be used to restore data
+Outputs INSERT queries into file. This dump can be used to restore data
after wiping out.
By default creates files
diff --git a/rt/sbin/rt-test-dependencies.in b/rt/sbin/rt-test-dependencies.in
index ebea86c66..37ef32f64 100644
--- a/rt/sbin/rt-test-dependencies.in
+++ b/rt/sbin/rt-test-dependencies.in
@@ -189,6 +189,8 @@ File::ShareDir
File::Spec 0.8
HTML::Quoted
HTML::Scrubber 0.08
+HTML::TreeBuilder
+HTML::FormatText
Log::Dispatch 2.23
Sys::Syslog 0.16
Locale::Maketext 1.06
@@ -245,8 +247,6 @@ CGI::Emulate::PSGI
.
$deps{'MAILGATE'} = [ text_to_hash( << '.') ];
-HTML::TreeBuilder
-HTML::FormatText
Getopt::Long
LWP::UserAgent
Pod::Usage
@@ -288,7 +288,7 @@ Test::Builder 0.90 # needed for is_passing
Test::MockTime
Log::Dispatch::Perl
Test::WWW::Mechanize::PSGI
-Plack::Middleware::Test::StashWarnings
+Plack::Middleware::Test::StashWarnings 0.06
Test::LongString
.
@@ -339,6 +339,7 @@ Net::SMTP
$deps{'DASHBOARDS'} = [ text_to_hash( << '.') ];
HTML::RewriteAttributes 0.04
MIME::Types
+URI 1.59
.
$deps{'GRAPHVIZ'} = [ text_to_hash( << '.') ];
diff --git a/rt/sbin/standalone_httpd b/rt/sbin/standalone_httpd
index 78533c6e5..3386cd1fe 100755
--- a/rt/sbin/standalone_httpd
+++ b/rt/sbin/standalone_httpd
@@ -91,6 +91,7 @@ if (grep { m/help/ } @ARGV) {
require RT;
RT->LoadConfig();
+RT->InitLogging();
require Module::Refresh if RT->Config->Get('DevelMode');
require RT::Handle;
diff --git a/rt/sbin/standalone_httpd.in b/rt/sbin/standalone_httpd.in
index b438202dd..45c377088 100644
--- a/rt/sbin/standalone_httpd.in
+++ b/rt/sbin/standalone_httpd.in
@@ -91,6 +91,7 @@ if (grep { m/help/ } @ARGV) {
require RT;
RT->LoadConfig();
+RT->InitLogging();
require Module::Refresh if RT->Config->Get('DevelMode');
require RT::Handle;
diff --git a/rt/share/html/Admin/Articles/Elements/Topics b/rt/share/html/Admin/Articles/Elements/Topics
index 96ddaf00c..43ca9562c 100644
--- a/rt/share/html/Admin/Articles/Elements/Topics
+++ b/rt/share/html/Admin/Articles/Elements/Topics
@@ -105,7 +105,7 @@ $topic
% }
% if ($Action) {
% unless ($Action eq "Move" and grep {$_->getNodeValue->Id == $Modify} $Element->getAllChildren) {
-
<&|/l&>RT can make this custom field's values into hyperlinks to another service.&>
<&|/l&>Fill in this field with a URL.&>
-<&|/l, '__id__', '__CustomField__' &>RT will replace [_1] and [_2] with the record's id and the custom field's value, respectively.&>
+<&|/l_unsafe, '__id__', '__CustomField__' &>RT will replace [_1] and [_2] with the record's id and the custom field's value, respectively.&>
<&|/l&>Include page&>
@@ -121,7 +121,7 @@
<&|/l&>RT can include content from another web service when showing this custom field.&>
<&|/l&>Fill in this field with a URL.&>
-<&|/l, '__id__', '__CustomField__' &>RT will replace [_1] and [_2] with the record's id and the custom field's value, respectively.&>
+<&|/l_unsafe, '__id__', '__CustomField__' &>RT will replace [_1] and [_2] with the record's id and the custom field's value, respectively.&>
<&|/l&>Some browsers may only load content from the same domain as your RT server.&>
-<&|/l, 'root' &>You should be taken directly to a login page. You'll be able to log in with username of [_1] and the password you set earlier.&>
+<&|/l_unsafe, 'root' &>You should be taken directly to a login page. You'll be able to log in with username of [_1] and the password you set earlier.&>
diff --git a/rt/share/html/Install/Initialize.html b/rt/share/html/Install/Initialize.html
index 47d7616a1..0cc39aff6 100644
--- a/rt/share/html/Install/Initialize.html
+++ b/rt/share/html/Install/Initialize.html
@@ -125,7 +125,7 @@ if ( $Run ) {
$RT::Handle = RT::Handle->new;
RT::Init();
my $file = $RT::EtcPath . "/initialdata";
- ($status, $msg) = $RT::Handle->InsertData( $file );
+ ($status, $msg) = $RT::Handle->InsertData( $file, undef, disconnect_after => 0 );
}
unless ( $status ) {
push @errors, loc('ERROR: [_1]', $msg);
diff --git a/rt/share/html/Install/index.html b/rt/share/html/Install/index.html
index 61fb89e39..78069afe3 100644
--- a/rt/share/html/Install/index.html
+++ b/rt/share/html/Install/index.html
@@ -92,7 +92,7 @@
my @errors;
my $locked;
-my $file = File::Spec->catfile( $RT::EtcPath, 'RT_SiteConfig.pm' );
+my $file = RT::Installer->ConfigFile;
if ( ! -e $file ) {
# write a blank RT_SiteConfig.pm
diff --git a/rt/share/html/NoAuth/Logout.html b/rt/share/html/NoAuth/Logout.html
index b8e119af5..20024ccec 100755
--- a/rt/share/html/NoAuth/Logout.html
+++ b/rt/share/html/NoAuth/Logout.html
@@ -81,5 +81,5 @@ if (keys %session) {
}
$m->callback( %ARGS, CallbackName => 'AfterSessionDelete' );
-$m->notes->{LogoutURL} = $URL;
+$m->notes->{RefreshURL} = $URL;
%INIT>
diff --git a/rt/share/html/NoAuth/css/aileron/InHeader b/rt/share/html/NoAuth/css/aileron/InHeader
index aff24d8a6..e6d4cb311 100644
--- a/rt/share/html/NoAuth/css/aileron/InHeader
+++ b/rt/share/html/NoAuth/css/aileron/InHeader
@@ -45,9 +45,6 @@
%# those contributions and any derivatives thereof.
%#
%# END BPS TAGGED BLOCK }}}
-
diff --git a/rt/share/html/NoAuth/css/aileron/boxes.css b/rt/share/html/NoAuth/css/aileron/boxes.css
index f90ac9f77..ed6623cba 100644
--- a/rt/share/html/NoAuth/css/aileron/boxes.css
+++ b/rt/share/html/NoAuth/css/aileron/boxes.css
@@ -91,6 +91,10 @@
.titlebox .titlebox-title {
position: relative;
+ /* This is for [rt3 #19044]. Move it to an IE-specific file if it causes
+ * problems. If we remove CSS3PIE, it can also probably go away, although it
+ * probably won't hurt. */
+ z-index: 1;
}
.titlebox .titlebox-title a {
diff --git a/rt/share/html/NoAuth/css/aileron/msie-pie.css b/rt/share/html/NoAuth/css/aileron/msie-pie.css
deleted file mode 100644
index baa9ebed3..000000000
--- a/rt/share/html/NoAuth/css/aileron/msie-pie.css
+++ /dev/null
@@ -1,58 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2012 Best Practical Solutions, LLC
-%#
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-.search-result-views,
-.ticket-transaction div.metadata span.actions,
-div#ticket-history div.downloadattachment,
-.ticket-transaction div.metadata span.type,
-.titlebox,
-.titlebox .titlebox-title .right,
-.titlebox .titlebox-title .left,
-div#footer,
-div#body {
- behavior: url(<%RT->Config->Get('WebPath')%>/NoAuth/css/images/PIE.htc);
-}
diff --git a/rt/share/html/NoAuth/css/images/PIE.htc b/rt/share/html/NoAuth/css/images/PIE.htc
deleted file mode 100644
index 6a40cef47..000000000
--- a/rt/share/html/NoAuth/css/images/PIE.htc
+++ /dev/null
@@ -1,77 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/rt/share/html/NoAuth/css/web2/InHeader b/rt/share/html/NoAuth/css/web2/InHeader
index 408a541ea..a083eec21 100644
--- a/rt/share/html/NoAuth/css/web2/InHeader
+++ b/rt/share/html/NoAuth/css/web2/InHeader
@@ -73,6 +73,3 @@ jQuery(document).ready(function(){
jQuery("#prefs-menu").addClass("sf-menu sf-js-enabled").supersubs().superfish().supposition({ speed: 'fast' });
});
-
diff --git a/rt/share/html/NoAuth/css/web2/msie-pie.css b/rt/share/html/NoAuth/css/web2/msie-pie.css
deleted file mode 100644
index 73d76d091..000000000
--- a/rt/share/html/NoAuth/css/web2/msie-pie.css
+++ /dev/null
@@ -1,60 +0,0 @@
-%# BEGIN BPS TAGGED BLOCK {{{
-%#
-%# COPYRIGHT:
-%#
-%# This software is Copyright (c) 1996-2012 Best Practical Solutions, LLC
-%#
-%#
-%# (Except where explicitly superseded by other copyright notices)
-%#
-%#
-%# LICENSE:
-%#
-%# This work is made available to you under the terms of Version 2 of
-%# the GNU General Public License. A copy of that license should have
-%# been provided with this software, but in any event can be snarfed
-%# from www.gnu.org.
-%#
-%# This work is distributed in the hope that it will be useful, but
-%# WITHOUT ANY WARRANTY; without even the implied warranty of
-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%# General Public License for more details.
-%#
-%# You should have received a copy of the GNU General Public License
-%# along with this program; if not, write to the Free Software
-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-%# 02110-1301 or visit their web page on the internet at
-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-%#
-%#
-%# CONTRIBUTION SUBMISSION POLICY:
-%#
-%# (The following paragraph is not intended to limit the rights granted
-%# to you to modify and distribute this software under the terms of
-%# the GNU General Public License and is only of importance to you if
-%# you choose to contribute your changes and enhancements to the
-%# community by submitting them to Best Practical Solutions, LLC.)
-%#
-%# By intentionally submitting any modifications, corrections or
-%# derivatives to this work, or any other work intended for use with
-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-%# you are the copyright holder for those contributions and you grant
-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-%# royalty-free, perpetual, license to use, copy, create derivative
-%# works based on those contributions, and sublicense and distribute
-%# those contributions and any derivatives thereof.
-%#
-%# END BPS TAGGED BLOCK }}}
-.search-result-views,
-.ticket-transaction div.metadata span.actions,
-div#ticket-history div.downloadattachment,
-.ticket-transaction div.metadata span.type,
-.titlebox,
-.titlebox .titlebox-title .right,
-.titlebox .titlebox-title .left,
-div#footer,
-#main-navigation,
-#page-navigation,
-div#body {
- behavior: url(<%RT->Config->Get('WebPath')%>/NoAuth/css/images/PIE.htc);
-}
diff --git a/rt/share/html/NoAuth/js/titlebox-state.js b/rt/share/html/NoAuth/js/titlebox-state.js
index 51996377e..a5e5dac20 100644
--- a/rt/share/html/NoAuth/js/titlebox-state.js
+++ b/rt/share/html/NoAuth/js/titlebox-state.js
@@ -46,7 +46,7 @@
%#
%# END BPS TAGGED BLOCK }}}
function createCookie(name,value,days) {
- var path = "<%RT->Config->Get('WebPath')%>" ? "<%RT->Config->Get('WebPath')%>" : "/";
+ var path = <%RT->Config->Get('WebPath')|n,j%> ? <%RT->Config->Get('WebPath')|n,j%> : "/";
if (days) {
var date = new Date();
diff --git a/rt/share/html/NoAuth/js/userautocomplete.js b/rt/share/html/NoAuth/js/userautocomplete.js
index db244d16c..b4f678c76 100644
--- a/rt/share/html/NoAuth/js/userautocomplete.js
+++ b/rt/share/html/NoAuth/js/userautocomplete.js
@@ -70,7 +70,7 @@ jQuery(function() {
continue;
var options = {
- source: "<% RT->Config->Get('WebPath')%>/Helpers/Autocomplete/Users"
+ source: <% RT->Config->Get('WebPath') |n,j%>+"/Helpers/Autocomplete/Users"
};
var queryargs = [];
diff --git a/rt/share/html/NoAuth/js/util.js b/rt/share/html/NoAuth/js/util.js
index 62ee922f9..5bfce411d 100644
--- a/rt/share/html/NoAuth/js/util.js
+++ b/rt/share/html/NoAuth/js/util.js
@@ -294,8 +294,8 @@ function ReplaceAllTextareas(encoded) {
textArea.parentNode.appendChild(typeField);
- CKEDITOR.replace(textArea.name,{width:'100%',height:'<% RT->Config->Get('MessageBoxRichTextHeight') %>'});
- CKEDITOR.basePath = "<%RT->Config->Get('WebPath')%>/NoAuth/RichText/";
+ CKEDITOR.replace(textArea.name,{width:'100%',height:<% RT->Config->Get('MessageBoxRichTextHeight') |n,j%>});
+ CKEDITOR.basePath = <%RT->Config->Get('WebPath')|n,j%>+"/NoAuth/RichText/";
jQuery("#" + textArea.name + "___Frame").addClass("richtext-editor");
}
diff --git a/rt/share/html/Prefs/Search.html b/rt/share/html/Prefs/Search.html
index fdd9c17dd..42aa16bbf 100644
--- a/rt/share/html/Prefs/Search.html
+++ b/rt/share/html/Prefs/Search.html
@@ -95,7 +95,7 @@ $ARGS{'OrderBy'} = join '|', grep defined && /\S/, (ref $ARGS{'OrderBy'})? @{$AR
my ( $AvailableColumns, $CurrentFormat );
( $ARGS{Format}, $AvailableColumns, $CurrentFormat ) = $m->comp(
'/Search/Elements/BuildFormatString',
- cfqueues => {}, %ARGS
+ %ARGS
);
if ($ARGS{'Save'}) {
diff --git a/rt/share/html/REST/1.0/Forms/ticket/default b/rt/share/html/REST/1.0/Forms/ticket/default
index 9ae803d89..9a2212b55 100755
--- a/rt/share/html/REST/1.0/Forms/ticket/default
+++ b/rt/share/html/REST/1.0/Forms/ticket/default
@@ -149,10 +149,16 @@ else {
}
# Set custom field
elsif ($k =~ /^$cf_spec/) {
- my $cf = RT::CustomField->new( RT->SystemUser );
- my $cfk = $1 || $2;
- unless($cf->LoadByName( Name => $cfk )) {
- push @comments, "# Invalid custom field name ($cfk)";
+ my $key = $1 || $2;
+
+ my $cf = RT::CustomField->new( $session{CurrentUser} );
+ $cf->LoadByName( Name => $key, Queue => $data{Queue} || $v{Queue} );
+ unless ( $cf->id ) {
+ $cf->LoadByName( Name => $key, Queue => 0 );
+ }
+
+ if (not $cf->id) {
+ push @comments, "# Invalid custom field name ($key)";
delete $data{$k};
next;
}
@@ -348,9 +354,15 @@ else {
}
# Set custom field
elsif ($key =~ /^$cf_spec/) {
- my $cf = RT::CustomField->new( RT->SystemUser );
$key = $1 || $2;
- if (not $cf->LoadByName( Name => $key )) {
+
+ my $cf = RT::CustomField->new( $session{CurrentUser} );
+ $cf->LoadByName( Name => $key, Queue => $ticket->Queue );
+ unless ( $cf->id ) {
+ $cf->LoadByName( Name => $key, Queue => 0 );
+ }
+
+ if (not $cf->id) {
$n = 0;
$s = "Unknown custom field.";
}
diff --git a/rt/share/html/REST/1.0/Forms/ticket/links b/rt/share/html/REST/1.0/Forms/ticket/links
index e2e1830fe..bf4f2575c 100755
--- a/rt/share/html/REST/1.0/Forms/ticket/links
+++ b/rt/share/html/REST/1.0/Forms/ticket/links
@@ -100,7 +100,8 @@ if ($changes) {
my $tick = RT::Ticket->new($session{CurrentUser});
$tick->Load($nkey);
if ($tick->Id) {
- $nkey = $uri->FromObject($tick);
+ $uri->FromObject($tick);
+ $nkey = $uri->URI;
}
else {
$n = 0;
diff --git a/rt/share/html/REST/1.0/Forms/transaction/default b/rt/share/html/REST/1.0/Forms/transaction/default
index 1ffa2b2a5..2e45f6707 100644
--- a/rt/share/html/REST/1.0/Forms/transaction/default
+++ b/rt/share/html/REST/1.0/Forms/transaction/default
@@ -49,7 +49,6 @@
%#
<%ARGS>
$id
-$args => undef
$format => undef
$fields => undef
%ARGS>
@@ -57,8 +56,6 @@ $fields => undef
my $trans = RT::Transactions->new($session{CurrentUser});
my ($c, $o, $k, $e) = ("", [], {} , "");
-chomp $args;
-my @arglist = split('/', $args);
my $tid = $id;
$trans->Limit(FIELD => 'Id', OPERATOR => '=', VALUE => $tid);
diff --git a/rt/share/html/REST/1.0/ticket/link b/rt/share/html/REST/1.0/ticket/link
index aa80b0de4..8d3345fa0 100755
--- a/rt/share/html/REST/1.0/ticket/link
+++ b/rt/share/html/REST/1.0/ticket/link
@@ -81,10 +81,9 @@ if ($id && $object && $id != $object) {
goto OUTPUT;
}
$id ||= $object;
-unless ($id =~ /^\d+$/ && $to =~ /^\d+$/) {
- my $bad = ($id !~ /^\d+$/) ? $id : $to;
+unless ($id =~ /^\d+$/) {
$output = $r->path_info. "\n";
- $output .= "Invalid ticket id: '$bad'.\n";
+ $output .= "Invalid ticket id: '$id'.\n";
$status = "400 Bad Request";
goto OUTPUT;
}
diff --git a/rt/share/html/Search/Build.html b/rt/share/html/Search/Build.html
index ae4c7ba78..b200f9050 100644
--- a/rt/share/html/Search/Build.html
+++ b/rt/share/html/Search/Build.html
@@ -78,7 +78,7 @@
- <& Elements/PickCriteria, query => $query{'Query'}, cfqueues => $queues &>
+ <& Elements/PickCriteria, query => $query{'Query'}, queues => $queues &>
<& /Elements/Submit, Label => loc('Add these terms'), SubmitId => 'AddClause', Name => 'AddClause'&>
<& /Elements/Submit, Label => loc('Add these terms and Search'), SubmitId => 'DoSearch', Name => 'DoSearch'&>
@@ -275,7 +275,7 @@ my ( $AvailableColumns, $CurrentFormat );
( $query{'Format'}, $AvailableColumns, $CurrentFormat ) = $m->comp(
'Elements/BuildFormatString',
%ARGS,
- cfqueues => $queues,
+ queues => $queues,
Format => $query{'Format'},
);
@@ -308,7 +308,7 @@ if ( $ARGS{'DoSearch'} ) {
SavedChartSearchId => $ARGS{'SavedChartSearchId'},
SavedSearchId => $saved_search{'Id'},
);
- RT::Interface::Web::Redirect(RT->Config->Get('WebPath') . '/Search/Results.html?' . $redir_query_string);
+ RT::Interface::Web::Redirect(RT->Config->Get('WebURL') . 'Search/Results.html?' . $redir_query_string);
$m->abort;
}
diff --git a/rt/share/html/Search/Chart.html b/rt/share/html/Search/Chart.html
index 884d1838a..070ce7cf7 100644
--- a/rt/share/html/Search/Chart.html
+++ b/rt/share/html/Search/Chart.html
@@ -124,7 +124,7 @@ my %query;
-<&|/l, $m->scomp('Elements/SelectChartType', Name => 'ChartStyle', Default => $ChartStyle), $m->scomp('Elements/SelectGroupBy', Name => 'PrimaryGroupBy', Query => $ARGS{Query}, Default => $PrimaryGroupBy)
+<&|/l_unsafe, $m->scomp('Elements/SelectChartType', Name => 'ChartStyle', Default => $ChartStyle), $m->scomp('Elements/SelectGroupBy', Name => 'PrimaryGroupBy', Query => $ARGS{Query}, Default => $PrimaryGroupBy)
&>[_1] chart by [_2]&>
&>
diff --git a/rt/share/html/Search/Elements/BuildFormatString b/rt/share/html/Search/Elements/BuildFormatString
index 376997229..a39287bff 100644
--- a/rt/share/html/Search/Elements/BuildFormatString
+++ b/rt/share/html/Search/Elements/BuildFormatString
@@ -48,7 +48,7 @@
<%ARGS>
$Format => RT->Config->Get('DefaultSearchResultFormat')
-%cfqueues => ()
+%queues => ()
$Face => undef
$Size => undef
@@ -111,17 +111,11 @@ my @fields = (
$m->callback( CallbackOnce => 1, CallbackName => 'SetFieldsOnce', Fields => \@fields );
my $CustomFields = RT::CustomFields->new( $session{'CurrentUser'});
-foreach my $id (keys %cfqueues) {
+foreach my $id (keys %queues) {
# Gotta load up the $queue object, since queues get stored by name now. my $id
my $queue = RT::Queue->new($session{'CurrentUser'});
$queue->Load($id);
- unless ($queue->id) {
- # XXX TODO: This ancient code dates from a former developer
- # we have no idea what it means or why cfqueues are so encoded.
- $id =~ s/^.'*(.*).'*$/$1/;
- $queue->Load($id);
- }
- $CustomFields->LimitToQueue($queue->Id);
+ $CustomFields->LimitToQueue($queue->Id) if $queue->Id;
}
$CustomFields->LimitToGlobal;
diff --git a/rt/share/html/Search/Elements/Chart b/rt/share/html/Search/Elements/Chart
index 01b78c712..be05da315 100644
--- a/rt/share/html/Search/Elements/Chart
+++ b/rt/share/html/Search/Elements/Chart
@@ -130,10 +130,10 @@ my ($i,$total);
);
%perl>
<&|/l, @strong &>Search for tickets by entering [_1]id[_2] numbers, subject words [_1]"in quotes"[_2], [_1]queues[_2] by name, Owners by [_1]username[_2], Requestors by [_1]email address[_2], and ticket [_1]statuses[_2].&>
+
<&|/l_unsafe, @strong &>Search for tickets by entering [_1]id[_2] numbers, subject words [_1]"in quotes"[_2], [_1]queues[_2] by name, Owners by [_1]username[_2], Requestors by [_1]email address[_2], and ticket [_1]statuses[_2].&>
<&|/l&>Any word not recognized by RT is searched for in ticket subjects.&>
@@ -74,7 +74,7 @@
% }
% }
-
<&|/l, map { "$_" } qw(initial active inactive any) &>Entering [_1], [_2], [_3], or [_4] limits results to tickets with one of the respective types of statuses. Any individual status name limits results to just the statuses named.&>
+
<&|/l_unsafe, map { "$_" } qw(initial active inactive any) &>Entering [_1], [_2], [_3], or [_4] limits results to tickets with one of the respective types of statuses. Any individual status name limits results to just the statuses named.&>
% if (RT->Config->Get('OnlySearchActiveTicketsInSimpleSearch', $session{'CurrentUser'})) {
% my $status_str = join ', ', map { loc($_) } RT::Queue->ActiveStatusArray;
@@ -82,13 +82,13 @@
% }
-
<&|/l, map { "$_" } 'queue:"Example Queue"', 'owner:email@example.com' &>Start the search term with the name of a supported field followed by a colon, as in [_1] and [_2], to explicitly specify the search type.&>
+
<&|/l_unsafe, map { "$_" } 'queue:"Example Queue"', 'owner:email@example.com' &>Start the search term with the name of a supported field followed by a colon, as in [_1] and [_2], to explicitly specify the search type.&>
-
<&|/l, 'cf.Name:value' &>CFs may be searched using a similar syntax as above with [_1].&>
+
<&|/l_unsafe, 'cf.Name:value' &>CFs may be searched using a similar syntax as above with [_1].&>