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 +- rt/share/html/Admin/Elements/SelectNewGroupMembers | 8 +- rt/share/html/Admin/Groups/index.html | 2 +- rt/share/html/Admin/Tools/Queries.html | 4 +- rt/share/html/Admin/Tools/Shredder/Dumps/dhandler | 5 +- .../Admin/Tools/Shredder/Elements/Error/NoStorage | 2 +- .../Tools/Shredder/Elements/Object/RT--Attachment | 2 +- .../Tools/Shredder/Elements/Object/RT--Ticket | 2 +- .../Admin/Tools/Shredder/Elements/Object/RT--User | 2 +- rt/share/html/Admin/Users/Modify.html | 8 +- rt/share/html/Admin/Users/index.html | 2 +- rt/share/html/Approvals/Elements/PendingMyApproval | 4 +- rt/share/html/Articles/Article/Edit.html | 1 + rt/share/html/Articles/Article/Elements/EditTopics | 55 ++-- .../html/Articles/Article/ExtractIntoClass.html | 2 +- rt/share/html/Articles/Topics.html | 249 +++++--------- rt/share/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 +- rt/share/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 +- rt/share/html/Elements/RT__CustomField/ColumnMap | 8 +- rt/share/html/Elements/RT__Dashboard/ColumnMap | 2 +- rt/share/html/Elements/RT__Queue/ColumnMap | 6 +- rt/share/html/Elements/SelectOwner | 10 +- rt/share/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 + .../html/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 +- rt/share/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 +- rt/share/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 +- rt/share/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 +- .../Ticket/Graphs/Elements/EditGraphProperties | 2 +- rt/share/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; } @@ -261,6 +263,12 @@ sub WithoutMember { 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', OPERATOR => 'IS', 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) { -
  • Id%>" value="<&|/l&><%$Action%> here" />
  • +
  • Id%>" value="<% $Action eq 'Move' ? loc('Move here') : loc('Add here') %>" />
  • % } % } diff --git a/rt/share/html/Admin/CustomFields/Modify.html b/rt/share/html/Admin/CustomFields/Modify.html index 20c3e9c4e..4ed86b60b 100644 --- a/rt/share/html/Admin/CustomFields/Modify.html +++ b/rt/share/html/Admin/CustomFields/Modify.html @@ -113,7 +113,7 @@
    <&|/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.
    diff --git a/rt/share/html/Admin/Elements/EditCustomFields b/rt/share/html/Admin/Elements/EditCustomFields index aa7b62204..d9d9134e7 100755 --- a/rt/share/html/Admin/Elements/EditCustomFields +++ b/rt/share/html/Admin/Elements/EditCustomFields @@ -128,6 +128,7 @@ if ( $MoveCustomFieldDown ) { { if ( $UpdateCFs ) { foreach my $cf_id ( @AddCustomField ) { my $CF = RT::CustomField->new( $session{'CurrentUser'} ); + $CF->SetContextObject( $Object ); $CF->Load( $cf_id ); unless ( $CF->id ) { push @results, loc("Couldn't load CustomField #[_1]", $cf_id); @@ -138,6 +139,7 @@ if ( $UpdateCFs ) { } foreach my $cf_id ( @RemoveCustomField ) { my $CF = RT::CustomField->new( $session{'CurrentUser'} ); + $CF->SetContextObject( $Object ); $CF->Load( $cf_id ); unless ( $CF->id ) { push @results, loc("Couldn't load CustomField #[_1]", $cf_id); @@ -153,6 +155,7 @@ $m->callback(CallbackName => 'UpdateExtraFields', Results => \@results, Object = my $applied_cfs = RT::CustomFields->new( $session{'CurrentUser'} ); $applied_cfs->LimitToLookupType($lookup); $applied_cfs->LimitToGlobalOrObjectId($id); +$applied_cfs->SetContextObject( $Object ); $applied_cfs->ApplySortOrder; my $not_applied_cfs = RT::CustomFields->new( $session{'CurrentUser'} ); diff --git a/rt/share/html/Admin/Elements/EditRights b/rt/share/html/Admin/Elements/EditRights index e5b9908b5..e67359313 100644 --- a/rt/share/html/Admin/Elements/EditRights +++ b/rt/share/html/Admin/Elements/EditRights @@ -110,13 +110,13 @@ for my $category (@$Principals) { id="AddPrincipalForRights-<% lc $AddPrincipal %>" /> %# XXX - Hide this select from w3m? - , this.value)" name="<% $id %>-Category" class="CF-<%$CustomField->id%>-Edit"> % foreach my $cat (@category) { % my ($depth, $name) = @$cat; @@ -66,12 +66,12 @@ <%ARGS> diff --git a/rt/share/html/Elements/MessageBox b/rt/share/html/Elements/MessageBox index 2943cab4e..61995e057 100755 --- a/rt/share/html/Elements/MessageBox +++ b/rt/share/html/Elements/MessageBox @@ -45,7 +45,7 @@ %# those contributions and any derivatives thereof. %# %# END BPS TAGGED BLOCK }}} - @@ -68,13 +68,16 @@ if ( $IncludeSignature and my $text = $session{'CurrentUser'}->UserObj->Signatur # wrap="something" seems to really break IE + richtext my $wrap_type = ''; if ( not RT->Config->Get('MessageBoxRichText', $session{'CurrentUser'}) ) { - $wrap_type = qq(wrap="$Wrap"); + $wrap_type = 'wrap="' . $m->interp->apply_escapes($Wrap, 'h') . '"'; } -# If there's no cols specified, we want to set the width to 100% -my $cols = 'style="width: 100%"'; -if ( defined $Width and length $Width ) { - $cols = qq(cols="$Width"); +# If there's no cols specified, we want to set the width to 100% in CSS +my $width_attr; +if ($Width) { + $width_attr = 'cols'; +} else { + $width_attr = 'style'; + $Width = 'width: 100%'; } diff --git a/rt/share/html/Elements/RT__CustomField/ColumnMap b/rt/share/html/Elements/RT__CustomField/ColumnMap index 06e2674ca..ecb219d9e 100644 --- a/rt/share/html/Elements/RT__CustomField/ColumnMap +++ b/rt/share/html/Elements/RT__CustomField/ColumnMap @@ -120,8 +120,10 @@ my $COLUMN_MAP = { my $name = 'RemoveCustomField'; my $checked = $m->request_args->{ $name .'All' }? 'checked="checked"': ''; - return \qq{}; + return \qq{}; }, value => sub { my $id = $_[0]->id; @@ -137,7 +139,7 @@ my $COLUMN_MAP = { elsif ( $arg ) { $checked = 'checked="checked"' if $arg == $id; } - return \qq{} + return \qq{} }, }, MoveCF => { diff --git a/rt/share/html/Elements/RT__Dashboard/ColumnMap b/rt/share/html/Elements/RT__Dashboard/ColumnMap index 8bc4383d8..6c366ec78 100644 --- a/rt/share/html/Elements/RT__Dashboard/ColumnMap +++ b/rt/share/html/Elements/RT__Dashboard/ColumnMap @@ -111,7 +111,7 @@ my $COLUMN_MAP = { } } - return \(''.$frequency.''); + return \'', $frequency, \''; }, }, ShowURL => { diff --git a/rt/share/html/Elements/RT__Queue/ColumnMap b/rt/share/html/Elements/RT__Queue/ColumnMap index 00655c5eb..e08dd7c91 100644 --- a/rt/share/html/Elements/RT__Queue/ColumnMap +++ b/rt/share/html/Elements/RT__Queue/ColumnMap @@ -84,12 +84,16 @@ my $COLUMN_MAP = { title => 'Encrypt', # loc value => sub { return $_[0]->Encrypt? $_[0]->loc('yes') : $_[0]->loc('no') }, }, + Lifecycle => { + title => 'Lifecycle', + attribute => 'Lifecycle', + value => sub { return $_[0]->Lifecycle->Name }, + }, }; foreach my $field (qw( Name Description CorrespondAddress CommentAddress InitialPriority FinalPriority DefaultDueIn - Lifecycle )) { $COLUMN_MAP->{$field} = { title => $field, diff --git a/rt/share/html/Elements/SelectOwner b/rt/share/html/Elements/SelectOwner index cc64e247b..37a5971ac 100755 --- a/rt/share/html/Elements/SelectOwner +++ b/rt/share/html/Elements/SelectOwner @@ -59,8 +59,12 @@ if ($TicketObj) { @objects = ($TicketObj); } elsif ($QueueObj) { @objects = ($QueueObj); -} elsif ($cfqueues) { - @objects = keys %{$cfqueues}; +} elsif (%Queues) { + for my $name (keys %Queues) { + my $q = RT::Queue->new($session{'CurrentUser'}); + $q->Load($name); + push @objects, $q; + } } else { # Let's check rights on an empty queue object. that will do a search # for any queue. @@ -77,5 +81,5 @@ $m->callback( <%ARGS> $TicketObj => undef $QueueObj => undef -$cfqueues => undef +%Queues => () diff --git a/rt/share/html/Elements/SelectOwnerAutocomplete b/rt/share/html/Elements/SelectOwnerAutocomplete index cf2010a80..81b38386c 100644 --- a/rt/share/html/Elements/SelectOwnerAutocomplete +++ b/rt/share/html/Elements/SelectOwnerAutocomplete @@ -78,7 +78,7 @@ my $query = $m->comp('/Elements/QueryString', \n} ); + $m->out( qq{\n} ); } }; diff --git a/rt/share/html/Elements/ShowLink b/rt/share/html/Elements/ShowLink index 8913a32fb..1727fa397 100644 --- a/rt/share/html/Elements/ShowLink +++ b/rt/share/html/Elements/ShowLink @@ -45,7 +45,7 @@ %# those contributions and any derivatives thereof. %# %# END BPS TAGGED BLOCK }}} - + % if ($URI->IsLocal) { % my $member = $URI->Object; % my $has_name = UNIVERSAL::can($member, 'Name') || (UNIVERSAL::can($member, '_Accessible') && $member->_Accessible('Name', 'read')); @@ -69,3 +69,12 @@ <%ARGS> $URI => undef + +<%INIT> +my $href = $URI->Resolver->HREF; +if ( $URI->IsLocal ) { + my $base = RT->Config->Get('WebBaseURL'); + # URI->rel doesn't contain the leading '/' + $href = '/' . URI->new($href)->rel($base); +} + diff --git a/rt/share/html/Elements/ShowSearch b/rt/share/html/Elements/ShowSearch index 2b23181c2..4b96bbfda 100644 --- a/rt/share/html/Elements/ShowSearch +++ b/rt/share/html/Elements/ShowSearch @@ -64,12 +64,12 @@ my $query_link_url = RT->Config->Get('WebPath').'/Search/Results.html'; if ($SavedSearch) { my ( $container_object, $search_id ) = _parse_saved_search($SavedSearch); unless ( $container_object ) { - $m->out(loc("Either you have no rights to view saved search [_1] or identifier is incorrect", $SavedSearch)); + $m->out(loc("Either you have no rights to view saved search [_1] or identifier is incorrect", $m->interp->apply_escapes($SavedSearch, 'h'))); return; } $search = $container_object->Attributes->WithId($search_id); unless ( $search->Id && ref( $SearchArg = $search->Content ) eq 'HASH' ) { - $m->out(loc("Saved Search [_1] not found", $SavedSearch)) unless $IgnoreMissing; + $m->out(loc("Saved Search [_1] not found", $m->interp->apply_escapes($SavedSearch, 'h'))) unless $IgnoreMissing; return; } $SearchArg->{'SavedSearchId'} ||= $SavedSearch; @@ -79,7 +79,7 @@ if ($SavedSearch) { # XXX: dispatch to different handler here $query_display_component = '/Search/Elements/' . $SearchArg->{SearchType}; - $query_link_url = RT->Config->Get('WebURL') . "/Search/$SearchArg->{SearchType}.html"; + $query_link_url = RT->Config->Get('WebPath') . "/Search/$SearchArg->{SearchType}.html"; } elsif ($ShowCustomize) { $customize = RT->Config->Get('WebPath') . '/Search/Build.html?' . $m->comp( '/Elements/QueryString', @@ -93,7 +93,7 @@ if ($SavedSearch) { if ($custom->Description eq $Name) { $search = $custom; last } } unless ($search && $search->id) { - $m->out("Predefined search $Name not found"); + $m->out(loc("Predefined search [_1] not found", $m->interp->apply_escapes($Name, 'h'))); return; } } diff --git a/rt/share/html/Elements/ShowUser b/rt/share/html/Elements/ShowUser index 044ec4c84..365497765 100644 --- a/rt/share/html/Elements/ShowUser +++ b/rt/share/html/Elements/ShowUser @@ -51,7 +51,7 @@ # $Address is Email::Address object my $comp = '/Elements/ShowUser'. ucfirst lc $style; -unless ( $m->comp_exists( $comp ) ) { +unless ( RT::Interface::Web->ComponentPathIsSafe($comp) and $m->comp_exists( $comp ) ) { $RT::Logger->error( 'Either system config or user #' . $session{'CurrentUser'}->id diff --git a/rt/share/html/Elements/Submit b/rt/share/html/Elements/Submit index cbf3f58e8..b7840d34b 100755 --- a/rt/share/html/Elements/Submit +++ b/rt/share/html/Elements/Submit @@ -52,10 +52,10 @@ id="<%$id%>" >
    % if ($CheckAll) { - + % } % if ($ClearAll) { - + % } % if ($Reset) { @@ -115,3 +115,13 @@ $ResetLabel => loc('Reset') $SubmitId => undef $id => undef +<%init> +my $match; +if (length $CheckboxName) { + $match = $m->interp->apply_escapes($CheckboxName,'j'); +} elsif (length $CheckboxNameRegex) { + $match = $CheckboxNameRegex; +} else { + $match = q{''}; +} + diff --git a/rt/share/html/Elements/Tabs b/rt/share/html/Elements/Tabs index 75b8160da..3193b488d 100755 --- a/rt/share/html/Elements/Tabs +++ b/rt/share/html/Elements/Tabs @@ -734,6 +734,9 @@ my $build_main_nav = sub { $current_search_menu->child( bulk => title => loc('Bulk Update'), path => "/Search/Bulk.html$args" ); $current_search_menu->child( chart => title => loc('Chart'), path => "/Search/Chart.html$args" ); + #formerly Callbacks/RTx-Calendar/Ticket/Element/Tabs/Default + $current_search_menu->child( calendar => title => loc('Calendar'), path => "/Search/Calendar.html$args" ); + my $more = $current_search_menu->child( more => title => loc('Feeds') ); $more->child( tsv => title => loc('TSV'), path => "/Search/Results.tsv$args" ); diff --git a/rt/share/html/Helpers/Autocomplete/CustomFieldValues b/rt/share/html/Helpers/Autocomplete/CustomFieldValues index b8b21e4fe..887302f0c 100644 --- a/rt/share/html/Helpers/Autocomplete/CustomFieldValues +++ b/rt/share/html/Helpers/Autocomplete/CustomFieldValues @@ -52,6 +52,17 @@ # Only autocomplete the last value my $term = (split /\n/, $ARGS{term} || '')[-1]; +my $abort = sub { + $r->content_type('application/json'); + $m->out(JSON::to_json( [] )); + $m->abort; +}; + +unless ( exists $ARGS{ContextType} and exists $ARGS{ContextId} ) { + RT->Logger->debug("No context provided"); + $abort->(); +} + my $CustomField; for my $k ( keys %ARGS ) { next unless $k =~ /^Object-.*?-\d*-CustomField-(\d+)-Values?$/; @@ -59,9 +70,38 @@ for my $k ( keys %ARGS ) { last; } -$m->abort unless $CustomField; +unless ( $CustomField ) { + RT->Logger->debug("No CustomField provided"); + $abort->(); +} + +my $SystemCustomFieldObj = RT::CustomField->new( RT->SystemUser ); +my ($id, $msg) = $SystemCustomFieldObj->LoadById( $CustomField ) ; +unless ( $id ) { + RT->Logger->debug("Invalid CustomField provided: $msg"); + $abort->(); +} + +my $context_object = $SystemCustomFieldObj->LoadContextObject( + $ARGS{ContextType}, $ARGS{ContextId} ); +$abort->() unless $context_object; + my $CustomFieldObj = RT::CustomField->new( $session{'CurrentUser'} ); -$CustomFieldObj->Load( $CustomField ); +if ( $SystemCustomFieldObj->ValidateContextObject($context_object) ) { + # drop our privileges that came from calling LoadContextObject as the System User + $context_object->new($session{'CurrentUser'}); + $context_object->LoadById($ARGS{ContextId}); + $CustomFieldObj->SetContextObject( $context_object ); +} else { + RT->Logger->debug("Invalid Context Object ".$context_object->id." for Custom Field ".$SystemCustomFieldObj->id); + $abort->(); +} + +($id, $msg) = $CustomFieldObj->LoadById( $CustomField ); +unless ( $CustomFieldObj->Name ) { + RT->Logger->debug("Current User cannot see this Custom Field, terminating"); + $abort->(); +} my $values = $CustomFieldObj->Values; $values->Limit( diff --git a/rt/share/html/Helpers/Toggle/ShowRequestor b/rt/share/html/Helpers/Toggle/ShowRequestor index bb90b9887..68e8a0517 100644 --- a/rt/share/html/Helpers/Toggle/ShowRequestor +++ b/rt/share/html/Helpers/Toggle/ShowRequestor @@ -47,7 +47,9 @@ %# END BPS TAGGED BLOCK }}} <%INIT> my $TicketTemplate = "/Ticket/Elements/ShowRequestorTickets$Status"; -$TicketTemplate = "/Ticket/Elements/ShowRequestorTicketsActive" unless $m->comp_exists($TicketTemplate); +$TicketTemplate = "/Ticket/Elements/ShowRequestorTicketsActive" + unless RT::Interface::Web->ComponentPathIsSafe($TicketTemplate) + and $m->comp_exists($TicketTemplate); my $user_obj = RT::User->new($session{CurrentUser}); my ($val, $msg) = $user_obj->Load($Requestor); unless ($val) { diff --git a/rt/share/html/Install/DatabaseType.html b/rt/share/html/Install/DatabaseType.html index 3312b57ec..68f8a67ed 100644 --- a/rt/share/html/Install/DatabaseType.html +++ b/rt/share/html/Install/DatabaseType.html @@ -58,7 +58,7 @@ <&|/l&>SQLite is a database that doesn't need a server or any configuration whatsoever. RT's authors recommend it for testing, demoing and development, but it's not quite right for a high-volume production RT server.

    -<&|/l, 'CPAN' &>If your preferred database isn't listed in the dropdown below, that means RT couldn't find a database driver for it installed locally. You may be able to remedy this by using [_1] to download and install DBD::MySQL, DBD::Oracle or DBD::Pg. +<&|/l_unsafe, 'CPAN' &>If your preferred database isn't listed in the dropdown below, that means RT couldn't find a database driver for it installed locally. You may be able to remedy this by using [_1] to download and install DBD::MySQL, DBD::Oracle or DBD::Pg.

    diff --git a/rt/share/html/Install/Finish.html b/rt/share/html/Install/Finish.html index ee81e70e4..24ac0ff71 100644 --- a/rt/share/html/Install/Finish.html +++ b/rt/share/html/Install/Finish.html @@ -53,7 +53,7 @@

    -<&|/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; 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 @@ -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); ); -Config->Get('WebURL') %>Search/Results.html?<%$QueryString%>><%$key%> +Config->Get('WebPath') %>/Search/Results.html?<%$QueryString%>><%$key%> -Config->Get('WebURL') %>Search/Results.html?<%$QueryString%>><%$value%> +Config->Get('WebPath') %>/Search/Results.html?<%$QueryString%>><%$value%> % } else { <% $key %> diff --git a/rt/share/html/Search/Elements/PickBasics b/rt/share/html/Search/Elements/PickBasics index 7223b75dc..db7d9f5c1 100644 --- a/rt/share/html/Search/Elements/PickBasics +++ b/rt/share/html/Search/Elements/PickBasics @@ -103,7 +103,7 @@ my @lines = ( Value => { Type => 'component', Path => '/Elements/SelectStatus', - Arguments => { SkipDeleted => 1 }, + Arguments => { SkipDeleted => 1, Queues => \%queues }, }, }, { @@ -124,7 +124,7 @@ my @lines = ( Value => { Type => 'component', Path => '/Elements/SelectOwner', - Arguments => { ValueAttribute => 'Name' }, + Arguments => { ValueAttribute => 'Name', Queues => \%queues }, }, }, { @@ -214,3 +214,6 @@ my @lines = ( $m->callback( Conditions => \@lines ); +<%ARGS> +%queues => () + diff --git a/rt/share/html/Search/Elements/PickCFs b/rt/share/html/Search/Elements/PickCFs index 4b9a88b77..f2dc21f68 100644 --- a/rt/share/html/Search/Elements/PickCFs +++ b/rt/share/html/Search/Elements/PickCFs @@ -50,21 +50,11 @@ % } <%INIT> my $CustomFields = RT::CustomFields->new( $session{'CurrentUser'}); -foreach my $id (keys %cfqueues) { - # Gotta load up the $queue object, since queues get stored by name now. my $id +foreach my $id (keys %queues) { + # Gotta load up the $queue object, since queues get stored by name now. 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/; - - # unescape internal quotes - $id =~ s/(\\(.))/$2 eq "'" ? "'" : $1/eg; - - $queue->Load($id); - } - $CustomFields->LimitToQueue($queue->Id); + $CustomFields->LimitToQueue($queue->Id) if $queue->Id; } $CustomFields->LimitToGlobal; $m->callback( @@ -124,10 +114,10 @@ while ( my $CustomField = $CustomFields->Next ) { push @lines, \%line; } -$m->callback( Conditions => \@lines, Queues => \%cfqueues ); +$m->callback( Conditions => \@lines, Queues => \%queues ); <%ARGS> -%cfqueues => undef +%queues => () diff --git a/rt/share/html/Search/Elements/PickCriteria b/rt/share/html/Search/Elements/PickCriteria index 5d0b8af5e..74547c7da 100644 --- a/rt/share/html/Search/Elements/PickCriteria +++ b/rt/share/html/Search/Elements/PickCriteria @@ -53,7 +53,7 @@ <& PickBasics &> <& PickCustomerFields &> -<& PickCFs, cfqueues => \%cfqueues &> +<& PickCFs, queues => \%queues &>
    @@ -69,5 +69,5 @@ <%ARGS> $addquery => 0 $query => undef -%cfqueues => undef +%queues => () diff --git a/rt/share/html/Search/Results.html b/rt/share/html/Search/Results.html index 0040d2a77..171b38d92 100755 --- a/rt/share/html/Search/Results.html +++ b/rt/share/html/Search/Results.html @@ -46,7 +46,7 @@ %# %# END BPS TAGGED BLOCK }}} <& /Elements/Header, Title => $title, - Refresh => $session{'tickets_refresh_interval'} || RT->Config->Get('SearchResultsRefreshInterval', $session{'CurrentUser'} ), + Refresh => $refresh, LinkRel => \%link_rel &> <& /Elements/Tabs &> <& /Elements/CollectionList, @@ -148,6 +148,16 @@ if ($ARGS{'TicketsRefreshInterval'}) { $session{'tickets_refresh_interval'} = $ARGS{'TicketsRefreshInterval'}; } +my $refresh = $session{'tickets_refresh_interval'} + || RT->Config->Get('SearchResultsRefreshInterval', $session{'CurrentUser'} ); + +if (RT->Config->Get('RestrictReferrer') and $refresh and not $m->request_args->{CSRF_Token}) { + my $token = RT::Interface::Web::StoreRequestToken( $session{'CurrentSearchHash'} ); + $m->notes->{RefreshURL} = RT->Config->Get('WebURL') + . "Search/Results.html?CSRF_Token=" + . $token; +} + my %link_rel; my $genpage = sub { return $m->comp( diff --git a/rt/share/html/Search/Simple.html b/rt/share/html/Search/Simple.html index 07bd2f4dc..4d7b1e3c5 100644 --- a/rt/share/html/Search/Simple.html +++ b/rt/share/html/Search/Simple.html @@ -60,7 +60,7 @@ % my @strong = qw( ); -

    <&|/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].

    % my $link_start = ''; % my $link_end = ''; -

    <&|/l, $link_start, $link_end &>For the full power of RT's searches, please visit the [_1]search builder interface[_2].

    +

    <&|/l_unsafe, $link_start, $link_end &>For the full power of RT's searches, please visit the [_1]search builder interface[_2].

    diff --git a/rt/share/html/SelfService/Elements/MyRequests b/rt/share/html/SelfService/Elements/MyRequests index 549d45839..76accfc9b 100755 --- a/rt/share/html/SelfService/Elements/MyRequests +++ b/rt/share/html/SelfService/Elements/MyRequests @@ -45,42 +45,34 @@ %# those contributions and any derivatives thereof. %# %# END BPS TAGGED BLOCK }}} -<&| /Widgets/TitleBox, title => $title &> +<&| /Widgets/TitleBox, title => $title &> <& /Elements/CollectionList, Title => $title, Format => $Format, Query => $Query, Order => @Order, OrderBy => @OrderBy, BaseURL => $BaseURL, - GenericQueryArgs => $GenericQueryArgs, - AllowSorting => $AllowSorting, + AllowSorting => 1, Class => 'RT::Tickets', Rows => $Rows, Page => $Page &> <%INIT> +my $title = loc("My [_1] tickets", $friendly_status); my $id = $session{'CurrentUser'}->id; -my $Query = "( " - . join( ' OR ', map "$_.id = $id", @roles ) - . ")"; +my $Query = "( Watcher.id = $id )"; if ( @status ) { - $Query .= " AND ( " - . join( ' OR ', map "Status = '$_'", @status ) - . " )"; + @status = map {s/(['\\])/\\$1/g; "Status = '$_'"} @status; + $Query .= " AND ( " . join(' OR ', @status ) . " )"; } my $Format = RT->Config->Get('DefaultSelfServiceSearchResultFormat'); - <%ARGS> $friendly_status => loc('open') -$title => loc("My [_1] tickets", $friendly_status) -@roles => ('Watcher') -@status => RT::Queue->ActiveStatusArray() +@status => () $BaseURL => undef $Page => 1 -$GenericQueryArgs => undef -$AllowSorting => 1 @Order => ('ASC') @OrderBy => ('Created') $Rows => 50 diff --git a/rt/share/html/SelfService/index.html b/rt/share/html/SelfService/index.html index a89296b19..29accf551 100755 --- a/rt/share/html/SelfService/index.html +++ b/rt/share/html/SelfService/index.html @@ -48,6 +48,8 @@ <& /SelfService/Elements/Header, Title => loc('Open tickets') &> <& /SelfService/Elements/MyRequests, %ARGS, + status => [ RT::Queue->ActiveStatusArray() ], + friendly_status => loc('open'), BaseURL => RT->Config->Get('WebPath') ."/SelfService/?", Page => $Page, &> diff --git a/rt/share/html/Ticket/Create.html b/rt/share/html/Ticket/Create.html index 9fb2b2bc5..f29dfbbcf 100755 --- a/rt/share/html/Ticket/Create.html +++ b/rt/share/html/Ticket/Create.html @@ -110,7 +110,7 @@ <& /Ticket/Elements/EditTransactionCustomFields, %ARGS, QueueObj => $QueueObj, InTable => 1 &> -% $m->callback( CallbackName => 'AfterBasics', QueueObj => $QueueObj ); +% $m->callback( CallbackName => 'AfterBasics', QueueObj => $QueueObj, ARGSRef => \%ARGS );
    @@ -304,41 +304,30 @@ if ($CloneTicket) { my $members = $CloneTicketObj->Members; my ( @members, @members_of, @refers, @refers_by, @depends, @depends_by ); my $refers = $CloneTicketObj->RefersTo; + my $get_link_value = sub { + my ($link, $type) = @_; + my $uri_method = $type . 'URI'; + my $local_method = 'Local' . $type; + my $uri = $link->$uri_method; + return if $uri->IsLocal and + $uri->Object and + $uri->Object->isa('RT::Ticket') and + $uri->Object->Type eq 'reminder'; + + return $link->$local_method || $uri->URI; + }; while ( my $refer = $refers->Next ) { - push @refers, $refer->LocalTarget; + my $refer_value = $get_link_value->($refer, 'Target'); + push @refers, $refer_value if defined $refer_value; } $clone->{'new-RefersTo'} = join ' ', @refers; my $refers_by = $CloneTicketObj->ReferredToBy; while ( my $refer_by = $refers_by->Next ) { - push @refers_by, $refer_by->LocalBase; + my $refer_by_value = $get_link_value->($refer_by, 'Base'); + push @refers_by, $refer_by_value if defined $refer_by_value; } $clone->{'RefersTo-new'} = join ' ', @refers_by; - if (0) { # Temporarily disabled - my $depends = $CloneTicketObj->DependsOn; - while ( my $depend = $depends->Next ) { - push @depends, $depend->LocalTarget; - } - $clone->{'new-DependsOn'} = join ' ', @depends; - - my $depends_by = $CloneTicketObj->DependedOnBy; - while ( my $depend_by = $depends_by->Next ) { - push @depends_by, $depend_by->LocalBase; - } - $clone->{'DependsOn-new'} = join ' ', @depends_by; - - while ( my $member = $members->Next ) { - push @members, $member->LocalBase; - } - $clone->{'MemberOf-new'} = join ' ', @members; - - my $members_of = $CloneTicketObj->MemberOf; - while ( my $member_of = $members_of->Next ) { - push @members_of, $member_of->LocalTarget; - } - $clone->{'new-MemberOf'} = join ' ', @members_of; - - } my $cfs = $CloneTicketObj->QueueObj->TicketCustomFields(); while ( my $cf = $cfs->Next ) { diff --git a/rt/share/html/Ticket/Display.html b/rt/share/html/Ticket/Display.html index 6deb65cac..0a29c9763 100755 --- a/rt/share/html/Ticket/Display.html +++ b/rt/share/html/Ticket/Display.html @@ -55,7 +55,7 @@ <& /Elements/ListActions, actions => \@Actions &> <& Elements/ShowUpdateStatus, Ticket => $TicketObj &> -% $m->callback( %ARGS, Ticket => $TicketObj, CallbackName => 'BeforeShowSummary' ); +% $m->callback( %ARGS, Ticket => $TicketObj, Transactions => $transactions, Attachments => $attachments, CallbackName => 'BeforeShowSummary' );
    <&| /Widgets/TitleBox, title => loc('Ticket metadata') &> <& /Ticket/Elements/ShowSummary, Ticket => $TicketObj, Attachments => $attachments &> @@ -63,7 +63,7 @@

    -% $m->callback( Ticket => $TicketObj, %ARGS, CallbackName => 'BeforeShowHistory' ); +% $m->callback( Ticket => $TicketObj, %ARGS, Transactions => $transactions, Attachments => $attachments, CallbackName => 'BeforeShowHistory' ); % if (not $ForceShowHistory and RT->Config->Get( 'DeferTransactionLoading', $session{'CurrentUser'} )) { <& /Ticket/Elements/ClickToShowHistory, @@ -83,6 +83,8 @@ % $m->callback( %ARGS, % Ticket => $TicketObj, +% Transactions => $transactions, +% Attachments => $attachments, % CallbackName => 'AfterShowHistory', % ); diff --git a/rt/share/html/Ticket/Elements/Bookmark b/rt/share/html/Ticket/Elements/Bookmark index 83931918d..30c9a4356 100644 --- a/rt/share/html/Ticket/Elements/Bookmark +++ b/rt/share/html/Ticket/Elements/Bookmark @@ -83,7 +83,7 @@ $Toggle => 0 % my $url = RT->Config->Get('WebPath') ."/Helpers/Toggle/TicketBookmark?id=". $id; - + % if ( $bookmarked ) { <% loc('Remove Bookmark') %> % } else { diff --git a/rt/share/html/Ticket/Elements/ClickToShowHistory b/rt/share/html/Ticket/Elements/ClickToShowHistory index 4461b9af6..5a9a477e0 100644 --- a/rt/share/html/Ticket/Elements/ClickToShowHistory +++ b/rt/share/html/Ticket/Elements/ClickToShowHistory @@ -47,7 +47,7 @@ %# END BPS TAGGED BLOCK }}} <%ARGS> diff --git a/rt/share/html/Ticket/Elements/FoldStanzaJS b/rt/share/html/Ticket/Elements/FoldStanzaJS index be6b2648c..4b0b4c466 100644 --- a/rt/share/html/Ticket/Elements/FoldStanzaJS +++ b/rt/share/html/Ticket/Elements/FoldStanzaJS @@ -47,4 +47,4 @@ %# END BPS TAGGED BLOCK }}} <%loc('Show quoted text')%>
    \ + onclick="fold_message_stanza(this, <%loc('Show quoted text') |n,j%>, <%loc('Hide quoted text') |n,j%>);"><%loc('Show quoted text')%>

    \ diff --git a/rt/share/html/Ticket/Elements/Reminders b/rt/share/html/Ticket/Elements/Reminders index 563b0f0cd..36d0d8e35 100644 --- a/rt/share/html/Ticket/Elements/Reminders +++ b/rt/share/html/Ticket/Elements/Reminders @@ -54,13 +54,14 @@ $Edit => 0 <%init> $Ticket = LoadTicket($id) if ($id); +my $resolve_status = $Ticket->QueueObj->Lifecycle->ReminderStatusOnResolve; my $count_reminders = RT::Reminders->new($session{'CurrentUser'}); $count_reminders->Ticket($Ticket->id); my $count_tickets = $count_reminders->Collection; if (!$ShowCompleted) { # XXX: don't break encapsulation if we can avoid it - $count_tickets->FromSQL('Type = "reminder" AND RefersTo = "'.$Ticket->id.'" AND Status != "resolved"'); + $count_tickets->FromSQL(q{Type = "reminder" AND RefersTo = "} . $Ticket->id . qq{" AND Status != "$resolve_status" }); } my $has_reminders = $count_tickets->Count; @@ -85,7 +86,7 @@ my $reminder_collection = $count_reminders->Collection; % my $visible = 0; % while ( my $reminder = $reminder_collection->Next ) { % $i++; -% if ( $reminder->Status eq 'resolved' && !$ShowCompleted ) { +% if ( $reminder->Status eq $resolve_status && !$ShowCompleted ) { % $i++; % } elsif ($Edit) { @@ -105,7 +106,7 @@ my $reminder_collection = $count_reminders->Collection; %# we must always include resolved reminders due to the browser %# checkbox-with-false-value issue % while ( my $reminder = $reminder_collection->Next ) { -% if ( $reminder->Status eq 'resolved' && !$ShowCompleted ) { +% if ( $reminder->Status eq $resolve_status && !$ShowCompleted ) { % } % } @@ -139,7 +140,7 @@ $Ticket $Index -Status eq 'resolved' ? 'checked="checked"' : '' |n %> /> +Status eq $Reminder->QueueObj->Lifecycle->ReminderStatusOnResolve ? 'checked="checked"' : '' |n %> /> <&|/l&>Subject: @@ -160,7 +161,7 @@ $Index % my $dueobj = $Reminder->DueObj; % my $overdue = $dueobj->Unix > 0 && $dueobj->Diff < 0 ? 1 : 0; -Status eq 'resolved' ? 'checked="checked"' : '' |n %> /> +Status eq $Reminder->QueueObj->Lifecycle->ReminderStatusOnResolve ? 'checked="checked"' : '' |n %> /> <% $Reminder->Subject %> <% $overdue ? '' : '' |n %><% $dueobj->AgeAsString || loc('Not set') %><% $overdue ? '' : '' |n %> <& /Elements/ShowUser, User => $Reminder->OwnerObj &> diff --git a/rt/share/html/Ticket/Elements/ShowHistory b/rt/share/html/Ticket/Elements/ShowHistory index b5e009c34..909ea01ee 100755 --- a/rt/share/html/Ticket/Elements/ShowHistory +++ b/rt/share/html/Ticket/Elements/ShowHistory @@ -60,11 +60,12 @@ if ($ShowDisplayModes or $ShowTitle) { if ($ShowDisplayModes) { $titleright = ''; - my $open_all = $m->interp->apply_escapes( loc("Show all quoted text"), 'h' ); - my $close_all = $m->interp->apply_escapes( loc("Hide all quoted text"), 'h' ); + my $open_all = $m->interp->apply_escapes( loc("Show all quoted text"), 'j' ); + my $open_html = $m->interp->apply_escapes( loc("Show all quoted text"), 'h' ); + my $close_all = $m->interp->apply_escapes( loc("Hide all quoted text"), 'j' ); $titleright .= '$open_all — "; + . qq{onclick="return toggle_all_folds(this, $open_all, $close_all);"} + . ">$open_html — "; if ($ShowHeaders) { $titleright .= qq{comp_exists($TicketTemplate); +$TicketTemplate = "ShowRequestorTicketsActive" + unless RT::Interface::Web->ComponentPathIsSafe($TicketTemplate) + and $m->comp_exists($TicketTemplate); <%ARGS> $Ticket=>undef diff --git a/rt/share/html/Ticket/Elements/UpdateCc b/rt/share/html/Ticket/Elements/UpdateCc index 392ee86b1..d062156c7 100644 --- a/rt/share/html/Ticket/Elements/UpdateCc +++ b/rt/share/html/Ticket/Elements/UpdateCc @@ -61,8 +61,7 @@ class="onetime onetimecc" type="checkbox" % my $clean_addr = $txn_addresses{$addr}->format; -% $clean_addr =~ s/'/\\'/g; - onClick="checkboxToInput('UpdateCc', 'UpdateCc-<%$addr%>','<%$clean_addr%>' );" + onClick="checkboxToInput('UpdateCc', <% "UpdateCc-$addr" |n,j%>, <%$clean_addr|n,j%> );" <% $ARGS{'UpdateCc-'.$addr} ? 'checked="checked"' : ''%> > <& /Elements/ShowUser, Address => $txn_addresses{$addr}&> %} @@ -77,8 +76,7 @@ class="onetime onetimebcc" type="checkbox" % my $clean_addr = $txn_addresses{$addr}->format; -% $clean_addr =~ s/'/\\'/g; - onClick="checkboxToInput('UpdateBcc', 'UpdateBcc-<%$addr%>','<%$clean_addr%>' );" + onClick="checkboxToInput('UpdateBcc', <% "UpdateBcc-$addr" |n,j%>, <%$clean_addr|n,j%> );" <% $ARGS{'UpdateBcc-'.$addr} ? 'checked="checked"' : ''%>> <& /Elements/ShowUser, Address => $txn_addresses{$addr}&> %} diff --git a/rt/share/html/Ticket/GnuPG.html b/rt/share/html/Ticket/GnuPG.html index 6269907ca..d15ce720a 100644 --- a/rt/share/html/Ticket/GnuPG.html +++ b/rt/share/html/Ticket/GnuPG.html @@ -51,7 +51,7 @@ % $m->callback( CallbackName => 'BeforeActionList', %ARGS, Actions => \@results, ARGSRef => \%ARGS ); <& /Elements/ListActions, actions => \@results &>
    - + <% loc('Return back to the ticket') %> <& /Elements/Submit, diff --git a/rt/share/html/Ticket/Graphs/Elements/EditGraphProperties b/rt/share/html/Ticket/Graphs/Elements/EditGraphProperties index e68aaed55..c5479e3f0 100644 --- a/rt/share/html/Ticket/Graphs/Elements/EditGraphProperties +++ b/rt/share/html/Ticket/Graphs/Elements/EditGraphProperties @@ -151,7 +151,7 @@ my $class = ''; $class = 'class="hidden"' if $Level != 1 && !@Default; <% loc('Show Tickets Properties on [_1] level', $Level) %> -(<% loc('open/close') %>): +(<% loc('open/close') %>): > % while ( my ($group, $list) = (splice @Available, 0, 2) ) {
    <% loc($group) %>: diff --git a/rt/share/html/Ticket/Graphs/Elements/ShowGraph b/rt/share/html/Ticket/Graphs/Elements/ShowGraph index 967a5e4aa..2163b81a8 100644 --- a/rt/share/html/Ticket/Graphs/Elements/ShowGraph +++ b/rt/share/html/Ticket/Graphs/Elements/ShowGraph @@ -66,6 +66,7 @@ $ARGS{'id'} = $id = $ticket->id; require RT::Graph::Tickets; my $graph = RT::Graph::Tickets->TicketLinks( %ARGS, + Graph => undef, Ticket => $ticket, ); diff --git a/rt/share/html/Ticket/Graphs/dhandler b/rt/share/html/Ticket/Graphs/dhandler index ba41445d0..89b1f37e1 100644 --- a/rt/share/html/Ticket/Graphs/dhandler +++ b/rt/share/html/Ticket/Graphs/dhandler @@ -65,6 +65,7 @@ unless ( $ticket->id ) { require RT::Graph::Tickets; my $graph = RT::Graph::Tickets->TicketLinks( %ARGS, + Graph => undef, Ticket => $ticket, ); diff --git a/rt/share/html/Ticket/ModifyLinks.html b/rt/share/html/Ticket/ModifyLinks.html index 28942e060..9dceb2a7a 100755 --- a/rt/share/html/Ticket/ModifyLinks.html +++ b/rt/share/html/Ticket/ModifyLinks.html @@ -51,7 +51,7 @@ % $m->callback(CallbackName => 'BeforeActionList', Actions => \@results, ARGSRef => \%ARGS, Ticket => $Ticket); <& /Elements/ListActions, actions => \@results &> - + % $m->callback( CallbackName => 'FormStart', ARGSRef => \%ARGS ); % my (@extra); diff --git a/rt/share/html/Widgets/ComboBox b/rt/share/html/Widgets/ComboBox index 238087f0a..69ac0793b 100644 --- a/rt/share/html/Widgets/ComboBox +++ b/rt/share/html/Widgets/ComboBox @@ -56,7 +56,7 @@ my $z_index = 9999;
    autocomplete="off" /> -
    % foreach my $value (@Values) { @@ -64,7 +64,7 @@ my $z_index = 9999;
    <%ARGS> diff --git a/rt/share/html/Widgets/TitleBoxStart b/rt/share/html/Widgets/TitleBoxStart index a03147785..cbcc5c3d5 100755 --- a/rt/share/html/Widgets/TitleBoxStart +++ b/rt/share/html/Widgets/TitleBoxStart @@ -48,7 +48,7 @@
    <% $rolledup ? " rolled-up" : ""%>" id="<% $id %>">
    "> % if ($hideable) { - + % } <% $title_href ? qq[] : '' | n diff --git a/rt/share/html/index.html b/rt/share/html/index.html index c24b6cdf4..d6e0b79b5 100755 --- a/rt/share/html/index.html +++ b/rt/share/html/index.html @@ -126,7 +126,7 @@ if ( $ARGS{'QuickCreate'} ) { if ( $ARGS{'q'} ) { - RT::Interface::Web::Redirect(RT->Config->Get('WebURL')."Search/Simple.html?q=".$m->interp->apply_escapes($ARGS{q})); + RT::Interface::Web::Redirect(RT->Config->Get('WebURL')."Search/Simple.html?q=".$m->interp->apply_escapes($ARGS{q}, 'u')); } diff --git a/rt/share/html/l b/rt/share/html/l index 6396bc640..9f1b34365 100755 --- a/rt/share/html/l +++ b/rt/share/html/l @@ -47,6 +47,6 @@ %# END BPS TAGGED BLOCK }}} <%init> my $hand = ($session{'CurrentUser'} ||= RT::CurrentUser->new)->LanguageHandle; - $m->print($hand->maketext($m->content,@_)); + $m->print($hand->maketext($m->content,map { $m->interp->apply_escapes($_, 'h') } @_)); return(1); diff --git a/rt/share/html/m/_elements/footer b/rt/share/html/m/_elements/footer index f3e0837cc..e0c023c9a 100644 --- a/rt/share/html/m/_elements/footer +++ b/rt/share/html/m/_elements/footer @@ -1,11 +1 @@ <& /elements/footer.html &> -% if ( 0 ) { - - - -% } diff --git a/rt/share/html/m/ticket/create b/rt/share/html/m/ticket/create index 7c23194c4..052a1a55f 100644 --- a/rt/share/html/m/ticket/create +++ b/rt/share/html/m/ticket/create @@ -6,6 +6,7 @@ $CloneTicket => undef $m->callback( CallbackName => "Init", ARGSRef => \%ARGS ); my $Queue = $ARGS{Queue}; +my $escape = sub { $m->interp->apply_escapes(shift, 'h') }; my $showrows = sub { my @pairs = @_; @@ -45,41 +46,30 @@ if ($CloneTicket) { my $members = $CloneTicketObj->Members; my ( @members, @members_of, @refers, @refers_by, @depends, @depends_by ); my $refers = $CloneTicketObj->RefersTo; + my $get_link_value = sub { + my ($link, $type) = @_; + my $uri_method = $type . 'URI'; + my $local_method = 'Local' . $type; + my $uri = $link->$uri_method; + return if $uri->IsLocal and + $uri->Object and + $uri->Object->isa('RT::Ticket') and + $uri->Object->Type eq 'reminder'; + + return $link->$local_method || $uri->URI; + }; while ( my $refer = $refers->Next ) { - push @refers, $refer->LocalTarget; + my $refer_value = $get_link_value->($refer, 'Target'); + push @refers, $refer_value if defined $refer_value; } $clone->{'new-RefersTo'} = join ' ', @refers; my $refers_by = $CloneTicketObj->ReferredToBy; while ( my $refer_by = $refers_by->Next ) { - push @refers_by, $refer_by->LocalBase; + my $refer_by_value = $get_link_value->($refer_by, 'Base'); + push @refers_by, $refer_by_value if defined $refer_by_value; } $clone->{'RefersTo-new'} = join ' ', @refers_by; - if (0) { # Temporarily disabled - my $depends = $CloneTicketObj->DependsOn; - while ( my $depend = $depends->Next ) { - push @depends, $depend->LocalTarget; - } - $clone->{'new-DependsOn'} = join ' ', @depends; - - my $depends_by = $CloneTicketObj->DependedOnBy; - while ( my $depend_by = $depends_by->Next ) { - push @depends_by, $depend_by->LocalBase; - } - $clone->{'DependsOn-new'} = join ' ', @depends_by; - - while ( my $member = $members->Next ) { - push @members, $member->LocalBase; - } - $clone->{'MemberOf-new'} = join ' ', @members; - - my $members_of = $CloneTicketObj->MemberOf; - while ( my $member_of = $members_of->Next ) { - push @members_of, $member_of->LocalTarget; - } - $clone->{'new-MemberOf'} = join ' ', @members_of; - - } my $cfs = $CloneTicketObj->QueueObj->TicketCustomFields(); while ( my $cf = $cfs->Next ) { @@ -218,7 +208,7 @@ if ((!exists $ARGS{'AddMoreAttach'}) and (defined($ARGS{'id'}) and $ARGS{'id'} e <%perl> $showrows->( - loc("Subject") => ''); + loc("Subject") => ''); <& /Elements/MessageBox, exists $ARGS{Content} ? (Default => $ARGS{Content}, IncludeSignature => 0 ) : ( QuoteTransaction => $QuoteTransaction ), Height => 5 &> @@ -382,12 +372,12 @@ $showrows->( <%perl> $showrows->( - loc("Depends on") => '', - loc("Depended on by") => '', - loc("Parents") => '', - loc("Children") => '', - loc("Refers to") => '', - loc("Referred to by") => '' + loc("Depends on") => '', + loc("Depended on by") => '', + loc("Parents") => '', + loc("Children") => '', + loc("Refers to") => '', + loc("Referred to by") => '' ); diff --git a/rt/share/html/m/ticket/show b/rt/share/html/m/ticket/show index e979da3e6..4190bd349 100644 --- a/rt/share/html/m/ticket/show +++ b/rt/share/html/m/ticket/show @@ -145,18 +145,18 @@ my $print_value = sub { } $m->out('') if defined $linked && length $linked; - # This section automatically populates a
    IncludeContentForValue ) { my $vid = $value->id; $m->out( '\n} ); - $m->out( qq{\n} ); + $m->out( qq{\n} ); } }; diff --git a/rt/share/html/m/tickets/search b/rt/share/html/m/tickets/search index 16864b4d3..e688ea821 100644 --- a/rt/share/html/m/tickets/search +++ b/rt/share/html/m/tickets/search @@ -31,7 +31,7 @@ my $search; if ( $custom->Description eq $name ) { $search = $custom; last } } unless ( $search && $search->id ) { - $m->out("Predefined search $name not found"); + $m->out(loc("Predefined search [_1] not found", $m->interp->apply_escapes($name, 'h'))); return; } } diff --git a/rt/t/api/date.t b/rt/t/api/date.t index 9756e51c4..6fcaa494b 100644 --- a/rt/t/api/date.t +++ b/rt/t/api/date.t @@ -4,7 +4,7 @@ use Test::MockTime qw(set_fixed_time restore_time); use DateTime; use warnings; use strict; -use RT::Test tests => 172; +use RT::Test tests => 173; use RT::User; use Test::Warn; @@ -85,9 +85,11 @@ my $current_user; my $date = RT::Date->new(RT->SystemUser); is($date->Unix, 0, "new date returns 0 in Unix format"); is($date->Get, '1970-01-01 00:00:00', "default is ISO format"); - is($date->Get(Format =>'SomeBadFormat'), - '1970-01-01 00:00:00', - "don't know format, return ISO format"); + warning_like { + is($date->Get(Format =>'SomeBadFormat'), + '1970-01-01 00:00:00', + "don't know format, return ISO format"); + } qr/Invalid date formatter/; is($date->Get(Format =>'W3CDTF'), '1970-01-01T00:00:00Z', "W3CDTF format with defaults"); diff --git a/rt/t/api/tickets.t b/rt/t/api/tickets.t index cabb00e50..50d08f756 100644 --- a/rt/t/api/tickets.t +++ b/rt/t/api/tickets.t @@ -2,7 +2,7 @@ use strict; use warnings; use RT; -use RT::Test tests => 16; +use RT::Test tests => 18; { @@ -101,3 +101,16 @@ ok( $unlimittickets->Count > 0, "UnLimited tickets object should return tickets" } + +{ + my $tickets = RT::Tickets->new( RT->SystemUser ); + $tickets->Limit( FIELD => 'id', OPERATOR => '>', VALUE => 0 ); + my $count = $tickets->Count(); + ok $count > 1, "found more than one ticket"; + undef $count; + + $tickets->Limit( FIELD => 'id', OPERATOR => '=', VALUE => 1, ENTRYAGGREGATOR => 'none' ); + $count = $tickets->Count(); + ok $count == 1, "found one ticket"; +} + diff --git a/rt/t/lifecycles/basics.t b/rt/t/lifecycles/basics.t index 40e239186..5825a105d 100644 --- a/rt/t/lifecycles/basics.t +++ b/rt/t/lifecycles/basics.t @@ -177,7 +177,7 @@ diag "deleted -> X via modify, only open is available"; my @form_values = $input->possible_values; ok scalar @form_values, 'some options in the UI'; - is join('-', @form_values), '-open', 'only open and default available'; + is join('-', @form_values), '-deleted-open', 'only default, current and open available'; } diag "check illegal values and transitions"; diff --git a/rt/t/mail/mime_decoding.t b/rt/t/mail/mime_decoding.t index b02f9795f..7515e2c41 100644 --- a/rt/t/mail/mime_decoding.t +++ b/rt/t/mail/mime_decoding.t @@ -1,7 +1,7 @@ #!/usr/bin/perl use strict; use warnings; -use RT::Test nodb => 1, tests => 8; +use RT::Test nodb => 1, tests => 9; use_ok('RT::I18N'); @@ -59,11 +59,29 @@ diag q{newline and encoded file name}; diag q{rfc2231}; { my $str = -"filename*=ISO-8859-1''%74%E9%73%74%2E%74%78%74 filename*=ISO-8859-1''%74%E9%73%74%2E%74%78%74"; +"attachment; filename*=ISO-8859-1''%74%E9%73%74%2E%74%78%74"; is( - RT::I18N::DecodeMIMEWordsToEncoding( $str, 'utf-8' ), - 'filename=tést.txt filename=tést.txt', - 'right decodig' + RT::I18N::DecodeMIMEWordsToEncoding( $str, 'utf-8', 'Content-Disposition' ), + 'attachment; filename="tést.txt"', + 'right decoding' + ); +} + +diag q{rfc2231 param continuations}; +{ + # XXX TODO: test various forms of the continuation stuff + # quotes around the values + my $hdr = <<'.'; +inline; + filename*0*=ISO-2022-JP'ja'%1b$B%3f7$7$$%25F%25%2d%259%25H%1b%28B; + filename*1*=%20; + filename*2*=%1b$B%25I%25%2d%25e%25a%25s%25H%1b%28B; + filename*3=.txt +. + is( + RT::I18N::DecodeMIMEWordsToEncoding( $hdr, 'utf-8', 'Content-Disposition' ), + 'inline; filename="新しいテキスト ドキュメント.txt"', + 'decoded continuations as one string' ); } diff --git a/rt/t/web/case-sensitivity.t b/rt/t/web/case-sensitivity.t index 276b7615a..f984bf3e4 100644 --- a/rt/t/web/case-sensitivity.t +++ b/rt/t/web/case-sensitivity.t @@ -75,7 +75,7 @@ my $cf; # test custom field values auto completer { - $m->get_ok('/Helpers/Autocomplete/CustomFieldValues?term=eNo&Object---CustomField-'. $cf->id .'-Value'); + $m->get_ok('/Helpers/Autocomplete/CustomFieldValues?term=eNo&Object---CustomField-'. $cf->id .'-Value&ContextId=1&ContextType=RT::Queue'); require JSON; is_deeply( JSON::from_json( $m->content ), diff --git a/rt/t/web/query_builder.t b/rt/t/web/query_builder.t index 0abbfaca8..5a64d4646 100644 --- a/rt/t/web/query_builder.t +++ b/rt/t/web/query_builder.t @@ -5,7 +5,7 @@ use HTTP::Request::Common; use HTTP::Cookies; use LWP; use Encode; -use RT::Test tests => 56; +use RT::Test tests => 70; my $cookie_jar = HTTP::Cookies->new; my ($baseurl, $agent) = RT::Test->started_ok; @@ -295,3 +295,34 @@ diag "click advanced, enter a valid SQL, but the field is lower cased"; ); } +diag "make sure skipped order by field doesn't break search"; +{ + my $t = RT::Test->create_ticket( Queue => 'General', Subject => 'test' ); + ok $t && $t->id, 'created a ticket'; + + $agent->get_ok($url."Search/Edit.html"); + ok($agent->form_name('BuildQueryAdvanced'), "found the form"); + $agent->field("Query", "id = ". $t->id); + $agent->submit; + + $agent->follow_link_ok({id => 'page-results'}); + ok( $agent->find_link( + text => $t->id, + url_regex => qr{/Ticket/Display\.html}, + ), "link to the ticket" ); + + $agent->follow_link_ok({id => 'page-edit_search'}); + $agent->form_name('BuildQuery'); + $agent->field("OrderBy", 'Requestor.EmailAddress', 3); + $agent->submit; + $agent->form_name('BuildQuery'); + is $agent->value('OrderBy', 1), 'id'; + is $agent->value('OrderBy', 2), ''; + is $agent->value('OrderBy', 3), 'Requestor.EmailAddress'; + + $agent->follow_link_ok({id => 'page-results'}); + ok( $agent->find_link( + text => $t->id, + url_regex => qr{/Ticket/Display\.html}, + ), "link to the ticket" ); +} diff --git a/rt/t/web/redirect-after-login.t b/rt/t/web/redirect-after-login.t index d429d30d1..835b24c37 100644 --- a/rt/t/web/redirect-after-login.t +++ b/rt/t/web/redirect-after-login.t @@ -196,16 +196,17 @@ for my $path (qw(Prefs/Other.html /Prefs/Other.html)) { # test REST login response { + $agent = RT::Test::Web->new; my $requested = $url."REST/1.0/?user=root;pass=password"; $agent->get($requested); is($agent->status, 200, "Loaded a page"); is($agent->uri, $requested, "didn't redirect to /NoAuth/Login.html for REST"); - $agent->get_ok($url); - $agent->logout(); + $agent->get_ok($url."REST/1.0"); } # test REST login response for wrong pass { + $agent = RT::Test::Web->new; my $requested = $url."REST/1.0/?user=root;pass=passwrong"; $agent->get_ok($requested); is($agent->status, 200, "Loaded a page"); @@ -217,6 +218,7 @@ for my $path (qw(Prefs/Other.html /Prefs/Other.html)) { # test REST login response for no creds { + $agent = RT::Test::Web->new; my $requested = $url."REST/1.0/"; $agent->get_ok($requested); is($agent->status, 200, "Loaded a page"); diff --git a/rt/t/web/rest.t b/rt/t/web/rest.t index 5e7194c95..e38f201fb 100644 --- a/rt/t/web/rest.t +++ b/rt/t/web/rest.t @@ -1,7 +1,9 @@ #!/usr/bin/env perl use strict; use warnings; -use RT::Test tests => 18; +use RT::Interface::REST; + +use RT::Test tests => 22; my ($baseurl, $m) = RT::Test->started_ok; @@ -69,3 +71,82 @@ for ("id: ticket/1", $m->content_contains($_); } +# Create ticket 2 for testing ticket links +for (2 .. 3) { + $m->post("$baseurl/REST/1.0/ticket/edit", [ + user => 'root', + pass => 'password', + content => $text, + ], Content_Type => 'form-data'); + + $m->post( + "$baseurl/REST/1.0/ticket/1/links", + [ + user => 'root', + pass => 'password', + ], + Content_Type => 'form-data', + ); + + my $link_data = form_parse($m->content); + + push @{$link_data->[0]->[1]}, 'DependsOn'; + vpush($link_data->[0]->[2], 'DependsOn', $_); + + $m->post( + "$baseurl/REST/1.0/ticket/1/links", + [ + user => 'root', + pass => 'password', + content => form_compose($link_data), + ], + Content_Type => 'form-data', + ); + +} + +# See what links get reported for ticket 1. +$m->post( + "$baseurl/REST/1.0/ticket/1/links/show", + [ + user => 'root', + pass => 'password', + ], + Content_Type => 'form-data', +); + +# Verify that the link was added correctly. +my $content = form_parse($m->content); +my $depends_on = vsplit($content->[0]->[2]->{DependsOn}); +@$depends_on = sort @$depends_on; +like( + $depends_on->[0], qr{/ticket/2$}, + "Check ticket link.", +) or diag("'content' obtained:\n", $m->content); + +like( + $depends_on->[1], qr{/ticket/3$}, + "Check ticket link.", +) or diag("'content' obtained:\n", $m->content); + +$m->post( + "$baseurl/REST/1.0/ticket/2/links/show", + [ + user => 'root', + pass => 'password', + ], + Content_Type => 'form-data', +); +my ($link) = $m->content =~ m|DependedOnBy:.*ticket/(\d+)|; +is($link, 1, "Check ticket link.") or diag("'content' obtained:\n", $m->content); + +$m->post( + "$baseurl/REST/1.0/ticket/3/links/show", + [ + user => 'root', + pass => 'password', + ], + Content_Type => 'form-data', +); +($link) = $m->content =~ m|DependedOnBy:.*ticket/(\d+)|; +is($link, 1, "Check ticket link.") or diag("'content' obtained:\n", $m->content); diff --git a/rt/t/web/scrub.t b/rt/t/web/scrub.t index 6483a7500..612c6e210 100644 --- a/rt/t/web/scrub.t +++ b/rt/t/web/scrub.t @@ -30,13 +30,13 @@ use Test::LongString; { my $html = q[oh hai I'm some text]; - my $expected = q[oh hai I'm some text]; + my $expected = q[oh hai I'm some text]; is_string(scrub_html($html), $expected, "font lists"); } { my $html = q[oh hai I'm some text]; - my $expected = q[oh hai I'm some text]; + my $expected = q[oh hai I'm some text]; is_string(scrub_html($html), $expected, "outlook html"); } diff --git a/rt/t/web/ticket_forward.t b/rt/t/web/ticket_forward.t index be06ad976..1d74673de 100644 --- a/rt/t/web/ticket_forward.t +++ b/rt/t/web/ticket_forward.t @@ -227,6 +227,41 @@ diag "Forward Transaction with attachments but no 'content' part" if $ENV{TEST_V like( $mail, qr/image\/png/, 'att image content type' ); } } +RT::Test->clean_caught_mails; + +diag "Forward Ticket Template with a Subject: line" if $ENV{TEST_VERBOSE}; +{ + + require RT::Template; + my $template = RT::Template->new($RT::SystemUser); + $template->Load('Forward Ticket'); + + # prepend a Subject: line + $template->SetContent("Subject: OVERRIDING SUBJECT\n\n" . $template->Content); + + my $ticket = RT::Test->create_ticket( + Subject => 'test ticket', + Queue => 1, + ); + + $m->goto_ticket($ticket->Id); + + $m->follow_link_ok( + { id => 'page-actions-forward' }, + 'follow 1st Forward to forward ticket' + ); + + $m->submit_form( + form_name => 'ForwardMessage', + fields => { + To => 'rt-to@example.com', + }, + button => 'ForwardAndReturn' + ); + + my ($mail) = RT::Test->fetch_caught_mails; + like($mail, qr/Subject: OVERRIDING SUBJECT/); +} undef $m; done_testing; diff --git a/rt/t/web/ticket_links.t b/rt/t/web/ticket_links.t index cb30e92f9..efb615107 100644 --- a/rt/t/web/ticket_links.t +++ b/rt/t/web/ticket_links.t @@ -1,6 +1,6 @@ use strict; use warnings; -use RT::Test tests => 106; +use RT::Test tests => 146; my ( $baseurl, $m ) = RT::Test->started_ok; ok( $m->login, "Logged in" ); @@ -24,6 +24,16 @@ is( $deleted->Status, 'deleted', "deleted $deleted_id" ); $inactive->SetStatus('resolved'); is( $inactive->Status, 'resolved', 'resolved $inactive_id' ); +# Create an article for linking +require RT::Class; +my $class = RT::Class->new($RT::SystemUser); +$class->Create(Name => 'test class'); + +require RT::Article; +my $article = RT::Article->new($RT::SystemUser); + +$article->Create(Class => $class->Id, Name => 'test article'); + for my $type ( "DependsOn", "MemberOf", "RefersTo" ) { for my $c (qw/base target/) { my $id; @@ -105,6 +115,59 @@ for my $type ( "DependsOn", "MemberOf", "RefersTo" ) { ); $m->content_unlike( qr{$deleted_id.*?\[deleted\]}, "no deleted ticket", ); + + diag "[$type]: Testing that reminders don't get copied for $c tickets"; + { + my $ticket = RT::Test->create_ticket( + Subject => 'test ticket', + Queue => 1, + ); + + $m->goto_ticket($ticket->Id); + $m->form_name('UpdateReminders'); + $m->field('NewReminder-Subject' => 'hello test reminder subject'); + $m->click_button(value => 'Save'); + $m->text_contains('hello test reminder subject'); + + my $id = $ticket->Id; + my $type_value = my $link_field = $type; + if ($c eq 'base') { + $type_value = "new-$type_value"; + $link_field = "$link_field-$id"; + } + else { + $type_value = "$type_value-new"; + $link_field = "$id-$link_field"; + } + + if ($type eq 'RefersTo') { + $m->goto_ticket($ticket->Id); + $m->follow_link(id => 'page-links'); + + # add $baseurl as a link + $m->form_name('ModifyLinks'); + $m->field($link_field => "$baseurl/test_ticket_reference"); + $m->click('SubmitTicket'); + + # add an article as a link + $m->form_name('ModifyLinks'); + $m->field($link_field => 'a:' . $article->Id); + $m->click('SubmitTicket'); + } + + my $depends_on_url = sprintf( + '%s/Ticket/Create.html?Queue=%s&CloneTicket=%s&%s=%s', + $baseurl, '1', $id, $type_value, $id, + ); + $m->get_ok($depends_on_url); + $m->form_name('TicketCreate'); + $m->click_button(value => 'Create'); + $m->content_lacks('hello test reminder subject'); + if ($type eq 'RefersTo') { + $m->text_contains("$baseurl/test_ticket_reference"); + $m->text_contains("Article " . $article->Id . ': test article'); + } + } } } -- 2.11.0