[Rt-commit] rt branch, setowner-refactor, created. rt-3.8.8-194-g5f5935f

? sunnavy sunnavy at bestpractical.com
Mon Nov 15 23:49:13 EST 2010


The branch, setowner-refactor has been created
        at  5f5935fc820252672f798fc285bff45d5068ca0f (commit)

- Log -----------------------------------------------------------------
commit cae63fc21aef7e836324cb2b969117b838abaf33
Author: sunnavy <sunnavy at bestpractical.com>
Date:   Tue Sep 28 11:11:35 2010 +0800

    change bookmark toggle from id to class so we can toggle more elements

diff --git a/share/html/Helpers/Toggle/TicketBookmark b/share/html/Helpers/Toggle/TicketBookmark
index ecd6914..718eb9b 100644
--- a/share/html/Helpers/Toggle/TicketBookmark
+++ b/share/html/Helpers/Toggle/TicketBookmark
@@ -47,8 +47,9 @@
 %# END BPS TAGGED BLOCK }}}
 <%ARGS>
 $id
+$Toggle => 1
 </%ARGS>
 <%INIT>
-$m->comp('/Ticket/Elements/Bookmark', id => $id, Toggle => 1);
+$m->comp('/Ticket/Elements/Bookmark', id => $id, Toggle => $Toggle );
 $m->abort();
 </%INIT>
diff --git a/share/html/NoAuth/js/util.js b/share/html/NoAuth/js/util.js
index c99fdea..908b72b 100644
--- a/share/html/NoAuth/js/util.js
+++ b/share/html/NoAuth/js/util.js
@@ -315,3 +315,26 @@ function checkboxToInput(target,checkbox,val){
     }
 }
 
+function toggleTicketBookmark( id, url ) {
+    var elements = $$("span.toggle-"+id);
+    if ( elements.length ) {
+        if ( elements.length == 1 ) {
+            new Ajax.Request(url, {
+                onSuccess: function(response) {
+                    $(elements[0]).replace(response.responseText);
+                }
+                }
+            );
+        }
+        else {
+            new Ajax.Request(url);
+            new Ajax.Request(url+'&Toggle=0', {
+                onSuccess: function(response) {
+                    elements.each( function( item ) {
+                        item.replace(response.responseText);
+                    })
+                }
+            });
+        }
+    }
+}
diff --git a/share/html/Ticket/Elements/Bookmark b/share/html/Ticket/Elements/Bookmark
index ecf08c7..1cc608b 100644
--- a/share/html/Ticket/Elements/Bookmark
+++ b/share/html/Ticket/Elements/Bookmark
@@ -81,9 +81,9 @@ if ( $Toggle ) {
 $id
 $Toggle => 0
 </%ARGS>
-<span id="toggle-<% $id %>">
+<span id="toggle-<% $id %>" class="toggle-<% $id %>">
 % my $url = RT->Config->Get('WebPath') ."/Helpers/Toggle/TicketBookmark?id=". $id;
-<a align="right" href="<% $url %>" onclick="ahah('<% $url |n %>', 'toggle-<% $id |n %>'); return false;" >
+<a align="right" href="<% $url %>" onclick="toggleTicketBookmark('<% $id|n %>', '<% $url %>'); return false;">
 % if ( $bookmarked ) {
 <img src="<% RT->Config->Get('WebPath') %>/NoAuth/images/star.gif" alt="<% loc('Remove Bookmark') %>" style="border-style: none" />
 % } else {

commit 8105bc6c556e429915a2e566bfaa7661891e2115
Author: Tom Lanyon <tom at netspot.com.au>
Date:   Fri Aug 27 20:58:07 2010 +0930

    Logic bug in bin/fastcgi_server; allow use of --port.
    
    Patch below (against 3.8-trunk) fixes bin/fastcgi_server --port <foo>.
    Without this, it always listens on $RT::VarPath/fastcgi.sock.
    
    Regards,
    Tom

diff --git a/bin/fastcgi_server.in b/bin/fastcgi_server.in
index bf3fdcc..63df548 100644
--- a/bin/fastcgi_server.in
+++ b/bin/fastcgi_server.in
@@ -198,7 +198,7 @@ $ENV{'RT_WEBMUX_HEAVY_LOAD'} = 1;
 use File::Basename;
 require (dirname(__FILE__) .'/webmux.pl');
 
-unless ( $opt{'socket'} && $opt{'port'} ) {
+unless ( $opt{'socket'} || $opt{'port'} ) {
     require File::Spec;
     $opt{'socket'} = File::Spec->catfile($RT::VarPath, 'fastcgi.sock');
 }

commit 7eab3d7d5322dce5ed377eb739b3b8d102efb303
Author: sunnavy <sunnavy at bestpractical.com>
Date:   Fri Oct 1 08:17:31 2010 +0800

    missing a closing div tag

diff --git a/share/html/Ticket/Elements/Reminders b/share/html/Ticket/Elements/Reminders
index d87ad0c..25a69db 100644
--- a/share/html/Ticket/Elements/Reminders
+++ b/share/html/Ticket/Elements/Reminders
@@ -122,6 +122,7 @@ $reminder_collection = $Ticket->Reminders->Collection;
 <div>
 <&|/l&>New reminder:</&>
 <& SELF:NewReminder, Ticket => $Ticket &>
+</div>
 <%method NewReminder>
 <%args>
 $Ticket

commit 8342cef017077b3bd0dea087d710ab87de49c4b4
Author: sunnavy <sunnavy at bestpractical.com>
Date:   Sat Oct 2 07:29:13 2010 +0800

    missing a Approver->Name change

diff --git a/etc/initialdata b/etc/initialdata
index 7faa43b..9b5506b 100755
--- a/etc/initialdata
+++ b/etc/initialdata
@@ -359,7 +359,7 @@ Approver\'s notes: { $Notes }
 
 Greetings,
 
-Your ticket has been rejected by { eval { $Approval->OwnerObj->Name } }.
+Your ticket has been rejected by { eval { $Approver->Name } }.
 
 Approver\'s notes: { $Notes }
 '

commit 89a15ccf9867d902214817d123f32bd6f2e43f6d
Author: Shawn M Moore <sartak at bestpractical.com>
Date:   Wed Oct 6 18:58:01 2010 -0400

    Try safeguarding against searches without ->Content
    
        Possible 3.6.x upgrade issue?

diff --git a/share/html/Prefs/MyRT.html b/share/html/Prefs/MyRT.html
index 4761377..6abe338 100644
--- a/share/html/Prefs/MyRT.html
+++ b/share/html/Prefs/MyRT.html
@@ -123,7 +123,16 @@ my @sys_searches;
 for my $object (@objs) {
     for ($m->comp("/Search/Elements/SearchesForObject", Object => $object)) {
         my ($desc, $search) = @$_;
-        my $SearchType = $search->Content->{'SearchType'} || 'Ticket';
+
+        my $SearchType = 'Ticket';
+        if ((ref($search->Content)||'') eq 'HASH') {
+            $SearchType = $search->Content->{'SearchType'}
+                if $search->Content->{'SearchType'};
+        }
+        else {
+            $RT::Logger->debug("Search ".$search->id." ($desc) appears to have no Content");
+        }
+
         if ($object eq $sys && $SearchType eq 'Ticket') {
             push @items, ["system-$desc", $desc];
             push @sys_searches, [$desc, $search];

commit f7b602e68d04a92b0a860403720cc61cb868cc36
Author: sunnavy <sunnavy at bestpractical.com>
Date:   Fri Oct 8 14:45:07 2010 +0800

    tweak a tiny bit

diff --git a/t/web/attachment_encoding.t b/t/web/attachment_encoding.t
index 85c5bbc..ce83509 100644
--- a/t/web/attachment_encoding.t
+++ b/t/web/attachment_encoding.t
@@ -3,7 +3,7 @@
 use strict;
 use warnings;
 
-use RT::Test tests => 22;
+use RT::Test tests => 28;
 use Encode;
 my ( $baseurl, $m ) = RT::Test->started_ok;
 ok $m->login, 'logged in as root';
@@ -33,9 +33,10 @@ diag 'test without attachments' if $ENV{TEST_VERBOSE};
     my ( $id ) = $m->uri =~ /(\d+)$/;
     ok( $id, 'found attachment id' );
     my $attachment = RT::Attachment->new( $RT::SystemUser );
-
+    ok($attachment->Load($id), "load att $id");
     # let make original encoding to gbk
-    $attachment->AddHeader( 'X-RT-Original-Encoding' => 'gbk' );
+    ok( $attachment->SetHeader( 'X-RT-Original-Encoding' => 'gbk' ),
+        'set original encoding to gbk' );
     $m->get( $m->uri );
     $m->content_contains( '标题', 'has subject 标题' );
     $m->content_contains( '测试', 'has content 测试' );
@@ -70,9 +71,10 @@ diag 'test with attachemnts' if $ENV{TEST_VERBOSE};
     my ( $id ) = $m->uri =~ /(\d+)$/;
     ok( $id, 'found attachment id' );
     my $attachment = RT::Attachment->new( $RT::SystemUser );
-
+    ok($attachment->Load($id), "load att $id");
     # let make original encoding to gbk
-    $attachment->AddHeader( 'X-RT-Original-Encoding' => 'gbk' );
+    ok( $attachment->SetHeader( 'X-RT-Original-Encoding' => 'gbk' ),
+        'set original encoding to gbk' );
     $m->get( $m->uri );
     $m->content_lacks( '标题', 'does not have content 标题' );
     $m->content_contains( '测试', 'has content 测试' );
@@ -87,9 +89,11 @@ diag 'test with attachemnts' if $ENV{TEST_VERBOSE};
     ( $id ) = $m->uri =~ /(\d+)\D+$/;
     ok( $id, 'found attachment id' );
     $attachment = RT::Attachment->new( $RT::SystemUser );
+    ok($attachment->Load($id), "load att $id");
 
     # let make original encoding to gbk
-    $attachment->AddHeader( 'X-RT-Original-Encoding' => 'gbk' );
+    ok( $attachment->SetHeader( 'X-RT-Original-Encoding' => 'gbk' ),
+        'set original encoding to gbk' );
     $m->get( $m->uri );
     $m->content_contains( '附件', 'has content 附件' );
 

commit 3fcf977a7f1fa77f581eab7ee7d221d4152b6ff7
Author: sunnavy <sunnavy at bestpractical.com>
Date:   Fri Oct 8 14:48:08 2010 +0800

    customize out_method to encode right

diff --git a/lib/RT/Interface/Web/Handler.pm b/lib/RT/Interface/Web/Handler.pm
index cd54e85..b123734 100644
--- a/lib/RT/Interface/Web/Handler.pm
+++ b/lib/RT/Interface/Web/Handler.pm
@@ -153,7 +153,47 @@ sub NewApacheHandler {
 
 sub NewCGIHandler {
     require HTML::Mason::CGIHandler;
-    return NewHandler('HTML::Mason::CGIHandler', @_);
+    return NewHandler(
+        'HTML::Mason::CGIHandler',
+        out_method => sub {
+            my $m = HTML::Mason::Request->instance;
+            my $r = $m->cgi_request;
+
+            # Send headers if they have not been sent by us or by user.
+            $r->send_http_header unless $r->http_header_sent;
+
+            # Set up a default
+            $r->content_type('text/html; charset=utf-8')
+                unless $r->content_type;
+
+            if ( $r->content_type =~ /charset=([\w-]+)$/ ) {
+                my $enc = $1;
+                if ( lc $enc !~ /utf-?8$/ ) {
+                    for my $str (@_) {
+                        next unless $str;
+
+                        # only encode perl internal strings
+                        next unless utf8::is_utf8($str);
+                        $str = Encode::encode( $enc, $str );
+                    }
+                }
+            }
+
+            # default to utf8 encoding
+            for my $str (@_) {
+                next unless $str;
+                next unless utf8::is_utf8($str);
+                $str = Encode::encode( 'utf8', $str );
+            }
+
+            # We could perhaps install a new, faster out_method here that
+            # wouldn't have to keep checking whether headers have been
+            # sent and what the $r->method is.  That would require
+            # additions to the Request interface, though.
+            print STDOUT grep {defined} @_;
+        },
+        @_
+    );
 }
 
 sub NewHandler {

commit 13b6c93483d67f09c7c8990afeaa30d87f0baaf9
Author: sunnavy <sunnavy at bestpractical.com>
Date:   Fri Oct 8 15:27:27 2010 +0800

    make Approver->Name change in upgrade

diff --git a/etc/upgrade/3.8.9/content b/etc/upgrade/3.8.9/content
index 5c42b78..8a28f7d 100644
--- a/etc/upgrade/3.8.9/content
+++ b/etc/upgrade/3.8.9/content
@@ -37,4 +37,27 @@
             } while $found == 1000;
         }
     },
+    sub {
+        my $queue = RT::Queue->new( $RT::SystemUser );
+        $queue->Load('___Approvals');
+        return unless $queue->id;
+
+        for my $name (
+            'All Approvals Passed', 'Approval Passed', 'Approval Rejected'
+          )
+        {
+            my $template = RT::Template->new($RT::SystemUser);
+            $template->LoadQueueTemplate( Name => $name, Queue => $queue->id );
+            next unless $template->id;
+            my $content = $template->Content;
+
+            # there is only one OwnerObj->Name normally, so no need /g
+            if ( $content =~
+s!(?<=Your ticket has been (?:approved|rejected) by { eval { )\$Approval->OwnerObj->Name!\$Approver->Name!
+              )
+            {
+                $template->SetContent($content);
+            }
+        }
+    },
 );

commit 94e96b44439dfdea1e98e514f5835eed1be5a3d0
Author: sunnavy <sunnavy at bestpractical.com>
Date:   Wed Oct 13 20:12:44 2010 +0800

    case fix: the other 2 use lower cases

diff --git a/share/html/Ticket/Elements/ShowTransactionAttachments b/share/html/Ticket/Elements/ShowTransactionAttachments
index 9903ca4..aa74094 100644
--- a/share/html/Ticket/Elements/ShowTransactionAttachments
+++ b/share/html/Ticket/Elements/ShowTransactionAttachments
@@ -129,7 +129,7 @@ my $size_to_str = sub {
     my $size = shift;
     # show a download link
     if ( $size > 1024*1024 ) {
-        $size = loc( "[_1]M", int( $size / 1024 / 102.4 ) / 10 );
+        $size = loc( "[_1]m", int( $size / 1024 / 102.4 ) / 10 );
     }
     elsif ( $size > 1024 ) {
         $size = loc( "[_1]k", int( $size / 102.4 ) / 10 );

commit 0c497482a988d296d9188edb07f5e9b72efe3b0c
Author: Lyle Ross <Lyle.Ross at us-cert.gov>
Date:   Thu Oct 14 21:01:52 2010 +0900

    Updated RedHat layout:
    
    I installed RT with configure option --enable-layout=RH.
    
    Subsequent installs of RT-FM and RT-IR caused the installer to issue a
    message that it could not find the RT.pm package because it is located
    under ../rt and the installer is looking for ../rt3.
    
    The RH layout should use rt3, not rt.

diff --git a/config.layout b/config.layout
index 87078a1..9c1ce4c 100755
--- a/config.layout
+++ b/config.layout
@@ -144,25 +144,26 @@
   exec_prefix:		${prefix}
   bindir:		${exec_prefix}/bin
   sbindir:		${exec_prefix}/sbin
-  sysconfdir:		/etc/rt
+  sysconfdir:           /etc/rt3
   mandir:		${prefix}/man
-  libdir:		${prefix}/lib/rt
-  datadir:		/var/rt
+  libdir:               ${prefix}/lib/rt3
+  datadir:              /var/rt3
   htmldir:		${datadir}/html
   fontdir:		${datadir}/fonts
   manualdir:		${datadir}/doc
   plugindir:		${datadir}/plugins
   localstatedir:	/var
-  logfiledir:		${localstatedir}/log/rt
-  masonstatedir:	${localstatedir}/rt/mason_data
-  sessionstatedir:	${localstatedir}/rt/session_data
-  customdir:		${prefix}/local/rt
+  logfiledir:           ${localstatedir}/log/rt3
+  masonstatedir:        ${localstatedir}/rt3/mason_data
+  sessionstatedir:      ${localstatedir}/rt3/session_data
+  customdir:            ${prefix}/local/rt3
   custometcdir:		${customdir}/etc
   customhtmldir:	${customdir}/html
   customlexdir:		${customdir}/po
   customlibdir:		${customdir}/lib
 </Layout>
 
+
 <Layout relative>
   prefix:		/opt/rt3
   exec_prefix:		${prefix}

commit 90e89d2463651c3198981c0e32d2ec0ea1c480dc
Author: Thomas Sibley <trs at bestpractical.com>
Date:   Thu Oct 14 14:07:26 2010 -0400

    Fix a template content type bug when relying on $PreferredContentType
    
    If we don't default to the preferred content type, then we'll always
    convert text/html parts to plain text even if we prefer text/html.  This
    bug was introduced by 0a79e9c674d77fb31ccf423ad793817a408d079b.

diff --git a/lib/RT/Transaction_Overlay.pm b/lib/RT/Transaction_Overlay.pm
index 2f3ed26..225ba4a 100755
--- a/lib/RT/Transaction_Overlay.pm
+++ b/lib/RT/Transaction_Overlay.pm
@@ -315,7 +315,7 @@ defaults to textual.
 sub Content {
     my $self = shift;
     my %args = (
-        Type => '',
+        Type => $PreferredContentType || '',
         Quote => 0,
         Wrap  => 70,
         @_

commit 6c44641cd50c4f3ad631f4cb22292bb37a3c6c97
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Tue Oct 19 15:59:14 2010 +0400

    don't corupt queries with negative not-quoted numbers
    
    like 'Priority > -2'.

diff --git a/lib/RT/SQL.pm b/lib/RT/SQL.pm
index 799cf94..946a8ab 100644
--- a/lib/RT/SQL.pm
+++ b/lib/RT/SQL.pm
@@ -68,7 +68,7 @@ my @tokens = qw[VALUE AGGREGATOR OPERATOR OPEN_PAREN CLOSE_PAREN KEYWORD];
 use Regexp::Common qw /delimited/;
 my $re_aggreg      = qr[(?i:AND|OR)];
 my $re_delim       = qr[$RE{delimited}{-delim=>qq{\'\"}}];
-my $re_value       = qr[\d+|NULL|$re_delim];
+my $re_value       = qr[[+-]?\d+|NULL|$re_delim];
 my $re_keyword     = qr[[{}\w\.]+|$re_delim];
 my $re_op          = qr[=|!=|>=|<=|>|<|(?i:IS NOT)|(?i:IS)|(?i:NOT LIKE)|(?i:LIKE)]; # long to short
 my $re_open_paren  = qr[\(];
diff --git a/t/web/query_builder.t b/t/web/query_builder.t
index 02ed129..fa2c56d 100644
--- a/t/web/query_builder.t
+++ b/t/web/query_builder.t
@@ -5,7 +5,7 @@ use HTTP::Request::Common;
 use HTTP::Cookies;
 use LWP;
 use Encode;
-use RT::Test tests => 42;
+use RT::Test tests => 44;
 
 my $cookie_jar = HTTP::Cookies->new;
 my ($baseurl, $agent) = RT::Test->started_ok;
@@ -246,4 +246,13 @@ diag "input a condition, select (several conditions), click delete"
     );
 }
 
-1;
+diag "send query with not quoted negative number";
+{
+    my $response = $agent->get($url."Search/Build.html?Query=Priority%20>%20-2");
+    ok( $response->is_success, "Fetched " . $url."Search/Build.html" );
+
+    is( getQueryFromForm,
+        "Priority > -2",
+        "query is the same"
+    );
+}

commit 5e90c854bb5f752137fa2973c3044330eaf41bd0
Author: sunnavy <sunnavy at bestpractical.com>
Date:   Wed Oct 20 08:41:45 2010 +0800

    make content html to test more

diff --git a/t/web/html_template.t b/t/web/html_template.t
index 0976847..a2461dc 100644
--- a/t/web/html_template.t
+++ b/t/web/html_template.t
@@ -3,7 +3,7 @@
 use strict;
 use warnings;
 
-use RT::Test tests => 18;
+use RT::Test tests => 19;
 use Encode;
 my ( $baseurl, $m ) = RT::Test->started_ok;
 ok $m->login, 'logged in as root';
@@ -49,7 +49,8 @@ diag('create a ticket to see the autoreply mail') if $ENV{TEST_VERBOSE};
     $m->form_number(3);
     $m->submit_form(
         form_number => 3,
-        fields      => { Subject => '标题', Content => '测试', },
+        fields      => { Subject => '标题', Content => '<h1>测试</h1>',
+        ContentType => 'text/html' },
     );
     $m->content_like( qr/Ticket \d+ created/i, 'created the ticket' );
     $m->follow_link( text => 'Show' );
@@ -57,8 +58,8 @@ diag('create a ticket to see the autoreply mail') if $ENV{TEST_VERBOSE};
     $m->content_contains( 'éèà€', 'html has éèà€' );
     $m->content_contains( '标题',
         'html has ticket subject 标题' );
-    $m->content_contains( '测试',
-        'html has ticket content 测试' );
+    $m->content_contains( '&lt;h1&gt;测试&lt;/h1&gt;',
+        'html has ticket html content 测试' );
 }
 
 diag('test real mail outgoing') if $ENV{TEST_VERBOSE};
@@ -72,5 +73,6 @@ diag('test real mail outgoing') if $ENV{TEST_VERBOSE};
     like( $mail, qr/éèà€.*éèà€/s, 'mail has éèà€' );
     like( $mail, qr/标题.*标题/s,    'mail has ticket subject 标题' );
     like( $mail, qr/测试.*测试/s,    'mail has ticket content 测试' );
+    like( $mail, qr!<h1>测试</h1>!,    'mail has ticket html content 测试' );
 }
 

commit 87e5c159689c48631623cb135b6d7383310edf24
Author: Thomas Sibley <trs at bestpractical.com>
Date:   Thu Oct 21 14:23:50 2010 -0400

    Also bail out of the rich text editor for the iPad

diff --git a/share/html/Elements/HeaderJavascript b/share/html/Elements/HeaderJavascript
index c1a3d26..604f5c5 100644
--- a/share/html/Elements/HeaderJavascript
+++ b/share/html/Elements/HeaderJavascript
@@ -72,6 +72,7 @@ $onload => undef
         var sAgent = navigator.userAgent.toLowerCase();
         if (!FCKeditor_IsCompatibleBrowser() ||
             sAgent.indexOf('iphone') != -1 ||
+            sAgent.indexOf('ipad') != -1 ||
             sAgent.indexOf('android') != -1 )
             return false;
 

commit 917c211820590950f7eb0521f7f43b31aeed44c4
Author: Thomas Sibley <trs at bestpractical.com>
Date:   Fri Oct 15 17:11:14 2010 -0400

    Redirect to the desired page after logging in a user
    
    This prevents back button attacks in the form of resubmitting form data
    after the user has logged out of the browser (but not closed it).  See
    also rt3 #15804.
    
    For non-homepage hits, the browser now also gets redirected to
    /NoAuth/Login.html to get a login form.
    
    We use the session to store the next page URL (referenced by a hash),
    similar to how action results work.

diff --git a/lib/RT/Interface/Web.pm b/lib/RT/Interface/Web.pm
index 29deaa4..255f763 100755
--- a/lib/RT/Interface/Web.pm
+++ b/lib/RT/Interface/Web.pm
@@ -207,13 +207,30 @@ sub HandleRequest {
     unless ( _UserLoggedIn() ) {
         _ForceLogout();
 
-        # If the user is logging in, let's authenticate
-        if ( defined $ARGS->{user} && defined $ARGS->{pass} ) {
-            AttemptPasswordAuthentication($ARGS);
-        } else {
-            # if no credentials then show him login page
-            $HTML::Mason::Commands::m->comp( '/Elements/Login', %$ARGS );
-            $HTML::Mason::Commands::m->abort;
+        # Authenticate if the user is trying to login via user/pass query args
+        my ($authed, $msg) = AttemptPasswordAuthentication($ARGS);
+
+        unless ($authed) {
+            my $m = $HTML::Mason::Commands::m;
+            my $results = $msg ? LoginError($msg) : undef;
+
+            # REST urls get a special 401 response
+            if ($m->request_comp->path =~ '^/REST/\d+\.\d+/') {
+                $HTML::Mason::Commands::r->content_type("text/plain");
+                $m->error_format("text");
+                $m->out("RT/$RT::VERSION 401 Credentials required\n");
+                $m->out("\n$msg\n") if $msg;
+                $m->abort;
+            }
+            # Specially handle /index.html so that we get a nicer URL
+            elsif ( $m->request_comp->path eq '/index.html' ) {
+                my $next = SetNextPage(RT->Config->Get('WebURL'));
+                $m->comp('/NoAuth/Login.html', next => $next, results => $results);
+                $m->abort;
+            }
+            else {
+                TangentForLogin(results => $results);
+            }
         }
     }
 
@@ -245,6 +262,90 @@ sub _UserLoggedIn {
 
 }
 
+=head2 LoginError ERROR
+
+Pushes a login error into the Actions session store and returns the hash key.
+
+=cut
+
+sub LoginError {
+    my $new = shift;
+    my $key = Digest::MD5::md5_hex( rand(1024) );
+    push @{ $HTML::Mason::Commands::session{"Actions"}->{$key} ||= [] }, $new;
+    $HTML::Mason::Commands::session{'i'}++;
+    return $key;
+}
+
+=head2 SetNextPage [PATH]
+
+Intuits and stashes the next page in the sesssion hash.  If PATH is
+specified, uses that instead of the value of L<IntuitNextPage()>.  Returns
+the hash value.
+
+=cut
+
+sub SetNextPage {
+    my $next = shift || IntuitNextPage();
+    my $hash = Digest::MD5::md5_hex( $next . $$ );
+
+    $HTML::Mason::Commands::session{'NextPage'}->{$hash} = $next;
+    $HTML::Mason::Commands::session{'i'}++;  # Cargo culted, do we need this?
+    
+    SendSessionCookie();
+    return $hash;
+}
+
+
+=head2 TangentForLogin [HASH]
+
+Redirects to C</NoAuth/Login.html>, setting the value of L<IntuitNextPage> as
+the next page.  Optionally takes a hash which is dumped into query params.
+
+=cut
+
+sub TangentForLogin {
+    my $hash  = SetNextPage();
+    my %query = (@_, next => $hash);
+    my $login = RT->Config->Get('WebURL') . 'NoAuth/Login.html?';
+    $login .= $HTML::Mason::Commands::m->comp('/Elements/QueryString', %query);
+    Redirect($login);
+}
+
+=head2 IntuitNextPage
+
+Attempt to figure out the path to which we should return the user after a
+tangent.  The current request URL is used, or failing that, the C<WebURL>
+configuration variable.
+
+=cut
+
+sub IntuitNextPage {
+    my $req_uri;
+
+    # This includes any query parameters.  Redirect will take care of making
+    # it an absolute URL.
+    $req_uri = $ENV{'REQUEST_URI'} if $ENV{'REQUEST_URI'};
+
+    my $next = defined $req_uri ? $req_uri : RT->Config->Get('WebURL');
+
+    # sanitize $next
+    my $uri = URI->new($next);
+
+    # You get undef scheme with a relative uri like "/Search/Build.html"
+    unless (!defined($uri->scheme) || $uri->scheme eq 'http' || $uri->scheme eq 'https') {
+        $next = RT->Config->Get('WebURL');
+    }
+
+    # Make sure we're logging in to the same domain
+    # You can get an undef authority with a relative uri like "index.html"
+    my $uri_base_url = URI->new(RT->Config->Get('WebBaseURL'));
+    unless (!defined($uri->authority) || $uri->authority eq $uri_base_url->authority) {
+        $next = RT->Config->Get('WebURL');
+    }
+
+    return $next;
+}
+
 =head2 MaybeShowInstallModePage 
 
 This function, called exclusively by RT's autohandler, dispatches
@@ -284,6 +385,10 @@ sub MaybeShowNoAuthPage {
 
     return unless $m->base_comp->path =~ RT->Config->Get('WebNoAuthRegex');
 
+    # Don't show the login page to logged in users
+    Redirect(RT->Config->Get('WebURL'))
+        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 );
@@ -386,9 +491,15 @@ sub AttemptExternalAuth {
 
                 # we failed to successfully create the user. abort abort abort.
                 delete $HTML::Mason::Commands::session{'CurrentUser'};
-                $m->comp( '/Elements/Login', %$ARGS, Error => HTML::Mason::Commands::loc( 'Cannot create user: [_1]', $msg ) )
-                    if RT->Config->Get('WebFallbackToInternalAuth');;
-                $m->abort();
+
+                if (RT->Config->Get('WebFallbackToInternalAuth')) {
+                    my $key = LoginError(
+                        HTML::Mason::Commands::loc('Cannot create user: [_1]', $msg)
+                    );
+                    TangentForLogin( results => $key );
+                } else {
+                    $m->abort();
+                }
             }
         }
 
@@ -399,15 +510,19 @@ sub AttemptExternalAuth {
             $user = $orig_user;
 
             if ( RT->Config->Get('WebExternalOnly') ) {
-                $m->comp( '/Elements/Login', %$ARGS, Error => HTML::Mason::Commands::loc('You are not an authorized user') );
-                $m->abort();
+                my $key = LoginError(
+                    HTML::Mason::Commands::loc('You are not an authorized user')
+                );
+                TangentForLogin( results => $key );
             }
         }
     } elsif ( RT->Config->Get('WebFallbackToInternalAuth') ) {
         unless ( defined $HTML::Mason::Commands::session{'CurrentUser'} ) {
             # XXX unreachable due to prior defaulting in HandleRequest (check c34d108)
-            $m->comp( '/Elements/Login', %$ARGS, Error => HTML::Mason::Commands::loc('You are not an authorized user') );
-            $m->abort();
+            my $key = LoginError(
+                HTML::Mason::Commands::loc('You are not an authorized user')
+            );
+            TangentForLogin( results => $key );
         }
     } else {
 
@@ -420,7 +535,9 @@ sub AttemptExternalAuth {
 }
 
 sub AttemptPasswordAuthentication {
-    my $ARGS     = shift;
+    my $ARGS = shift;
+    return unless defined $ARGS->{user} && defined $ARGS->{pass};
+
     my $user_obj = RT::CurrentUser->new();
     $user_obj->Load( $ARGS->{user} );
 
@@ -428,15 +545,34 @@ sub AttemptPasswordAuthentication {
 
     unless ( $user_obj->id && $user_obj->IsPassword( $ARGS->{pass} ) ) {
         $RT::Logger->error("FAILED LOGIN for @{[$ARGS->{user}]} from $ENV{'REMOTE_ADDR'}");
-        $m->comp( '/Elements/Login', %$ARGS, Error => HTML::Mason::Commands::loc('Your username or password is incorrect'), );
         $m->callback( %$ARGS, CallbackName => 'FailedLogin', CallbackPage => '/autohandler' );
-        $m->abort;
+        return (0, HTML::Mason::Commands::loc('Your username or password is incorrect'));
     }
+    else {
+        $RT::Logger->info("Successful login for @{[$ARGS->{user}]} from $ENV{'REMOTE_ADDR'}");
+
+        # It's important to nab the next page from the session before we blow
+        # the session away
+        my $next = $HTML::Mason::Commands::session{'NextPage'}->{$ARGS->{'next'} || ''};
 
-    $RT::Logger->info("Successful login for @{[$ARGS->{user}]} from $ENV{'REMOTE_ADDR'}");
-    InstantiateNewSession();
-    $HTML::Mason::Commands::session{'CurrentUser'} = $user_obj;
-    $m->callback( %$ARGS, CallbackName => 'SuccessfulLogin', CallbackPage => '/autohandler' );
+        InstantiateNewSession();
+        $HTML::Mason::Commands::session{'CurrentUser'} = $user_obj;
+        SendSessionCookie();
+
+        $m->callback( %$ARGS, CallbackName => 'SuccessfulLogin', CallbackPage => '/autohandler' );
+
+        # Really the only time we don't want to redirect here is if we were
+        # passed user and pass as query params in the URL.
+        if ($next) {
+            Redirect($next);
+        }
+        elsif ($ARGS->{'next'}) {
+            # Invalid hash, but still wants to go somewhere, take them to /
+            Redirect(RT->Config->Get('WebURL'));
+        }
+
+        return (1, HTML::Mason::Commands::loc('Logged in'));
+    }
 }
 
 =head2 LoadSessionFromCookie
@@ -503,6 +639,10 @@ sub Redirect {
     untie $HTML::Mason::Commands::session;
     my $uri        = URI->new($redir_to);
     my $server_uri = URI->new( RT->Config->Get('WebURL') );
+    
+    # Make relative URIs absolute from the server host and scheme
+    $uri->scheme($server_uri->scheme) if not defined $uri->scheme;
+    $uri->host($server_uri->host)     if not defined $uri->host;
 
     # If the user is coming in via a non-canonical
     # hostname, don't redirect them to the canonical host,
diff --git a/share/html/Elements/ListActions b/share/html/Elements/ListActions
index de7584b..1e76bfe 100755
--- a/share/html/Elements/ListActions
+++ b/share/html/Elements/ListActions
@@ -46,7 +46,7 @@
 %# 
 %# END BPS TAGGED BLOCK }}}
 <div class="results">
-<&| /Widgets/TitleBox, title => loc('Results') &>
+<&| /Widgets/TitleBox, title => loc('Results'), %{$titlebox || {}} &>
   <ul class="action-results">
 % foreach my $action (@actions) {
     <li><%$action%></li>
@@ -90,5 +90,6 @@ return unless @actions;
 
 </%init>
 <%ARGS>
+$titlebox => {}
 @actions => undef
 </%ARGS>
diff --git a/share/html/Elements/Login b/share/html/Elements/Login
index 00a8ab8..a7820c3 100755
--- a/share/html/Elements/Login
+++ b/share/html/Elements/Login
@@ -45,42 +45,6 @@
 %# those contributions and any derivatives thereof.
 %# 
 %# END BPS TAGGED BLOCK }}}
-<%INIT>
-if ($m->request_comp->path =~ '^/REST/\d+\.\d+/') {
-    $r->content_type("text/plain");
-    $m->error_format("text");
-    $m->out("RT/$RT::VERSION 401 Credentials required\n");
-    $m->out("\n$Error\n") if $Error;
-    $m->abort;
-}
-
-my $req_uri;
-
-if (UNIVERSAL::can($r, 'uri') and $r->uri =~ m{.*/(.*)}) {
-    $req_uri = $1;
-}
-
-my $form_action = defined $goto             ? $goto
-                : defined $req_uri          ? $req_uri
-                :                             RT->Config->Get('WebPath')
-                ;
-
-# sanitize $form_action
-my $uri = URI->new($form_action);
-
-# You get undef scheme with a relative uri like "/Search/Build.html"
-unless (!defined($uri->scheme) || $uri->scheme eq 'http' || $uri->scheme eq 'https') {
-    $form_action = RT->Config->Get('WebPath');
-}
-
-# Make sure we're logging in to the same domain
-# You can get an undef authority with a relative uri like "index.html"
-my $uri_base_url = URI->new(RT->Config->Get('WebBaseURL'));
-unless (!defined($uri->authority) || $uri->authority eq $uri_base_url->authority) {
-    $form_action = RT->Config->Get('WebPath');
-}
-</%INIT>
-
 % $m->callback( %ARGS, CallbackName => 'Header' );
 <& /Elements/Header, Title => loc('Login'), Focus => 'user' &>
 
@@ -89,11 +53,12 @@ unless (!defined($uri->authority) || $uri->authority eq $uri_base_url->authority
 </div>
 
 <div id="body" class="login-body">
-% if ($Error) {
-<&| "/Widgets/TitleBox", title => loc('Error'), hideable => 0, class => 'error'  &>
-<% $Error %>
-</&>
-% }
+
+<& /Elements/ListActions,
+    title       => loc('Error'),
+    titlebox    => { class => 'error', hideable => 0 },
+    actions     => $actions
+&>
 
 % $m->callback( %ARGS, CallbackName => 'BeforeForm' );
 
@@ -101,7 +66,7 @@ unless (!defined($uri->authority) || $uri->authority eq $uri_base_url->authority
 <&| /Widgets/TitleBox, title => loc('Login'), titleright => $RT::VERSION, hideable => 0 &>
 
 % unless (RT->Config->Get('WebExternalAuth') and !RT->Config->Get('WebFallbackToInternalAuth')) {
-<form id="login" name="login" method="post" action="<% $form_action %>">
+<form id="login" name="login" method="post" action="<% RT->Config->Get('WebPath') %>/NoAuth/Login.html">
 
 <div class="input-row">
     <span class="label"><&|/l&>Username</&>:</span>
@@ -113,6 +78,8 @@ unless (!defined($uri->authority) || $uri->authority eq $uri_base_url->authority
     <span class="input"><input type="password" name="pass" autocomplete="off" /></span>
 </div>
 
+<input type="hidden" name="next" value="<% $next %>" />
+
 <div class="button-row">
     <span class="input"><input type="submit" class="button" value="<&|/l&>Login</&>" /></span>
 </div>
@@ -120,25 +87,6 @@ unless (!defined($uri->authority) || $uri->authority eq $uri_base_url->authority
 %# Give callbacks a chance to add more control elements
 % $m->callback( %ARGS );
 
-% # From mason 1.0.1 forward, this doesn't work. in fact, it breaks things.
-% # But on Mason 1.15 it's fixed again, so we still use it.
-% # The code below iterates through everything in the passed in arguments
-% # Preserving all the old parameters
-% # This would be easier, except mason is 'smart' and calls multiple values
-% # arrays rather than multiple hash keys
-% my $key; my $val;
-% foreach $key (keys %ARGS) {
-%  if (($key ne 'user') and ($key ne 'pass')) {
-% 	if (ref($ARGS{$key}) =~ /ARRAY/) {
-% 		foreach $val (@{$ARGS{$key}}) {
-<input type="hidden" class="hidden" name="<%$key %>" value="<% $val %>" />
-% 		}
-% 	}
-%	else {
-<input type="hidden" class="hidden" name="<% $key %>" value="<% $ARGS{$key} %>" />
-% 	}
-%  }
-% }
 </form>
 % }
 </&>
@@ -147,8 +95,7 @@ unless (!defined($uri->authority) || $uri->authority eq $uri_base_url->authority
 </div><!-- #login-body -->
 <& /Elements/Footer, Menu => 0 &>
 <%ARGS>
+$next => ''
 $user => ""
-$pass => undef
-$goto => undef
-$Error => undef
+$actions => undef
 </%ARGS>
diff --git a/share/html/Elements/ListActions b/share/html/NoAuth/Login.html
similarity index 66%
copy from share/html/Elements/ListActions
copy to share/html/NoAuth/Login.html
index de7584b..6a3084a 100755
--- a/share/html/Elements/ListActions
+++ b/share/html/NoAuth/Login.html
@@ -45,50 +45,8 @@
 %# those contributions and any derivatives thereof.
 %# 
 %# END BPS TAGGED BLOCK }}}
-<div class="results">
-<&| /Widgets/TitleBox, title => loc('Results') &>
-  <ul class="action-results">
-% foreach my $action (@actions) {
-    <li><%$action%></li>
-% }
-  </ul>
-</&>
-</div>
 <%init>
-
-# backward compatibility, don't use array in new code, but use keyed hash
-if ( ref( $session{'Actions'} ) eq 'ARRAY' ) {
-    unshift @actions, @{ delete $session{'Actions'} };
-}
-
-if ( ref( $session{'Actions'}{''} ) eq 'ARRAY' ) {
-    unshift @actions, @{ delete $session{'Actions'}{''} };
-}
-
-my $actions_pointer = $m->request_args->{'results'};
-
-if ($actions_pointer &&  ref( $session{'Actions'}->{$actions_pointer} ) eq 'ARRAY' ) {
-    unshift @actions, @{ delete $session{'Actions'}->{$actions_pointer} };
-}
-
-# XXX: run callbacks per row really crazy idea
- at actions =
-    grep $_,
-    grep {
-        my $skip;
-        $m->callback(
-            %ARGS,
-            row  => \$_,
-            skip => \$skip,
-            CallbackName => 'ModifyRow',
-        );
-        !$skip;
-    }
-    grep $_, @actions;
-
-return unless @actions;
-
+my ($good, $msg) = RT::Interface::Web::AttemptPasswordAuthentication(\%ARGS);
+$ARGS{'actions'} = [$msg] if not $good and $msg;
 </%init>
-<%ARGS>
- at actions => undef
-</%ARGS>
+<& /Elements/Login, %ARGS &>

commit fbf7e7066d7d17df218c19cb4ef7e52871aef762
Author: Thomas Sibley <trs at bestpractical.com>
Date:   Fri Oct 22 17:56:37 2010 -0400

    Add more entropy to the next page hash and yes, we need to ++ the session

diff --git a/lib/RT/Interface/Web.pm b/lib/RT/Interface/Web.pm
index 255f763..42270d1 100755
--- a/lib/RT/Interface/Web.pm
+++ b/lib/RT/Interface/Web.pm
@@ -286,10 +286,10 @@ the hash value.
 
 sub SetNextPage {
     my $next = shift || IntuitNextPage();
-    my $hash = Digest::MD5::md5_hex( $next . $$ );
+    my $hash = Digest::MD5::md5_hex($next . $$ . rand(1024));
 
     $HTML::Mason::Commands::session{'NextPage'}->{$hash} = $next;
-    $HTML::Mason::Commands::session{'i'}++;  # Cargo culted, do we need this?
+    $HTML::Mason::Commands::session{'i'}++;
     
     SendSessionCookie();
     return $hash;

commit 87408ef895b625d44512392de68c76a5c35fdd26
Author: Thomas Sibley <trs at bestpractical.com>
Date:   Fri Oct 22 17:57:04 2010 -0400

    Refactor the "error and tangent" pattern to a method

diff --git a/lib/RT/Interface/Web.pm b/lib/RT/Interface/Web.pm
index 42270d1..46d6133 100755
--- a/lib/RT/Interface/Web.pm
+++ b/lib/RT/Interface/Web.pm
@@ -311,6 +311,18 @@ sub TangentForLogin {
     Redirect($login);
 }
 
+=head2 TangentForLoginWithError ERROR
+
+Localizes the passed error message, stashes it with L<LoginError> and then
+calls L<TangentForLogin> with the appropriate results key.
+
+=cut
+
+sub TangentForLoginWithError {
+    my $key = LoginError(HTML::Mason::Commands::loc(@_));
+    TangentForLogin( results => $key );
+}
+
 =head2 IntuitNextPage
 
 Attempt to figure out the path to which we should return the user after a
@@ -493,10 +505,7 @@ sub AttemptExternalAuth {
                 delete $HTML::Mason::Commands::session{'CurrentUser'};
 
                 if (RT->Config->Get('WebFallbackToInternalAuth')) {
-                    my $key = LoginError(
-                        HTML::Mason::Commands::loc('Cannot create user: [_1]', $msg)
-                    );
-                    TangentForLogin( results => $key );
+                    TangentForLoginWithError('Cannot create user: [_1]', $msg);
                 } else {
                     $m->abort();
                 }
@@ -510,19 +519,13 @@ sub AttemptExternalAuth {
             $user = $orig_user;
 
             if ( RT->Config->Get('WebExternalOnly') ) {
-                my $key = LoginError(
-                    HTML::Mason::Commands::loc('You are not an authorized user')
-                );
-                TangentForLogin( results => $key );
+                TangentForLoginWithError('You are not an authorized user');
             }
         }
     } elsif ( RT->Config->Get('WebFallbackToInternalAuth') ) {
         unless ( defined $HTML::Mason::Commands::session{'CurrentUser'} ) {
             # XXX unreachable due to prior defaulting in HandleRequest (check c34d108)
-            my $key = LoginError(
-                HTML::Mason::Commands::loc('You are not an authorized user')
-            );
-            TangentForLogin( results => $key );
+            TangentForLoginWithError('You are not an authorized user');
         }
     } else {
 

commit 23a912a292ad0c3d4bca4d3f9aae39c0100bc042
Author: Thomas Sibley <trs at bestpractical.com>
Date:   Mon Oct 25 12:55:40 2010 -0400

    Don't show login errors as a bulleted list

diff --git a/share/html/NoAuth/css/web2/login.css b/share/html/NoAuth/css/web2/login.css
index 7aaf6b1..5cee016 100644
--- a/share/html/NoAuth/css/web2/login.css
+++ b/share/html/NoAuth/css/web2/login.css
@@ -45,6 +45,10 @@
 %# those contributions and any derivatives thereof.
 %# 
 %# END BPS TAGGED BLOCK }}}
+.login-body .action-results {
+    list-style: none;
+}
+
 #login-box hr {
  display: none;
 }

commit 6fa0a4e500a8be35a30fa9a7d9f7415ebb2d7405
Author: Thomas Sibley <trs at bestpractical.com>
Date:   Mon Oct 25 15:31:37 2010 -0400

    Fix passing login errors when on /

diff --git a/lib/RT/Interface/Web.pm b/lib/RT/Interface/Web.pm
index 46d6133..3459e73 100755
--- a/lib/RT/Interface/Web.pm
+++ b/lib/RT/Interface/Web.pm
@@ -212,7 +212,6 @@ sub HandleRequest {
 
         unless ($authed) {
             my $m = $HTML::Mason::Commands::m;
-            my $results = $msg ? LoginError($msg) : undef;
 
             # REST urls get a special 401 response
             if ($m->request_comp->path =~ '^/REST/\d+\.\d+/') {
@@ -225,11 +224,11 @@ sub HandleRequest {
             # Specially handle /index.html so that we get a nicer URL
             elsif ( $m->request_comp->path eq '/index.html' ) {
                 my $next = SetNextPage(RT->Config->Get('WebURL'));
-                $m->comp('/NoAuth/Login.html', next => $next, results => $results);
+                $m->comp('/NoAuth/Login.html', next => $next, actions => [$msg]);
                 $m->abort;
             }
             else {
-                TangentForLogin(results => $results);
+                TangentForLogin(results => ($msg ? LoginError($msg) : undef));
             }
         }
     }

commit e71c90f88c0f12936f3cae192ee4dc10bf11292c
Author: Thomas Sibley <trs at bestpractical.com>
Date:   Mon Oct 25 15:32:39 2010 -0400

    Do the right thing with //Ticket/Display.html when it's the REQUEST_URI
    
    Collapse leading slashes so that we interpret it as a relative URL and
    not a schema-less hostname + path.  REQUEST_URI should never have a
    schema-less URI.

diff --git a/lib/RT/Interface/Web.pm b/lib/RT/Interface/Web.pm
index 3459e73..53dc1b4 100755
--- a/lib/RT/Interface/Web.pm
+++ b/lib/RT/Interface/Web.pm
@@ -335,7 +335,13 @@ sub IntuitNextPage {
 
     # This includes any query parameters.  Redirect will take care of making
     # it an absolute URL.
-    $req_uri = $ENV{'REQUEST_URI'} if $ENV{'REQUEST_URI'};
+    if ($ENV{'REQUEST_URI'}) {
+        $req_uri = $ENV{'REQUEST_URI'};
+
+        # collapse multiple leading slashes so the first part doesn't look like
+        # a hostname of a schema-less URI
+        $req_uri =~ s{^/+}{/};
+    }
 
     my $next = defined $req_uri ? $req_uri : RT->Config->Get('WebURL');
 

commit a813943bb23cb4692dabb634c53a2213f8a2300d
Author: Thomas Sibley <trs at bestpractical.com>
Date:   Mon Oct 25 15:35:07 2010 -0400

    If we're adding the hostname to a relative URL, also add the port

diff --git a/lib/RT/Interface/Web.pm b/lib/RT/Interface/Web.pm
index 53dc1b4..c6c006d 100755
--- a/lib/RT/Interface/Web.pm
+++ b/lib/RT/Interface/Web.pm
@@ -650,7 +650,10 @@ sub Redirect {
     
     # Make relative URIs absolute from the server host and scheme
     $uri->scheme($server_uri->scheme) if not defined $uri->scheme;
-    $uri->host($server_uri->host)     if not defined $uri->host;
+    if (not defined $uri->host) {
+        $uri->host($server_uri->host);
+        $uri->port($server_uri->port);
+    }
 
     # If the user is coming in via a non-canonical
     # hostname, don't redirect them to the canonical host,

commit a87820750251000aba859cc45f510db27b0c1049
Author: Thomas Sibley <trs at bestpractical.com>
Date:   Mon Oct 25 15:35:38 2010 -0400

    Tests for the new login flow

diff --git a/t/web/redirect-after-login.t b/t/web/redirect-after-login.t
new file mode 100644
index 0000000..d39bb58
--- /dev/null
+++ b/t/web/redirect-after-login.t
@@ -0,0 +1,243 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+use RT::Test tests => 120;
+
+my ($baseurl, $agent) = RT::Test->started_ok;
+
+my $url = $agent->rt_base_url;
+diag $url if $ENV{TEST_VERBOSE};
+
+# test a login from the main page
+{
+    $agent->get_ok($url);
+    is($agent->{'status'}, 200, "Loaded a page");
+    is($agent->uri, $url, "didn't redirect to /NoAuth/Login.html for base URL");
+    ok($agent->current_form->find_input('user'));
+    ok($agent->current_form->find_input('pass'));
+    like($agent->current_form->action, qr{/NoAuth/Login\.html$}, "login form action is correct");
+
+    ok($agent->content =~ /username:/i);
+    $agent->field( 'user' => 'root' );
+    $agent->field( 'pass' => 'password' );
+
+    # the field isn't named, so we have to click link 0
+    $agent->click(0);
+    is( $agent->status, 200, "Fetched the page ok");
+    ok( $agent->content =~ /Logout/i, "Found a logout link");
+    is( $agent->uri, $url, "right URL" );
+    like( $agent->{redirected_uri}, qr{/NoAuth/Login\.html$}, "We redirected from login");
+    $agent->logout();
+}
+
+# test a bogus login from the main page
+{
+    $agent->get_ok($url);
+    is($agent->{'status'}, 200, "Loaded a page");
+    is($agent->uri, $url, "didn't redirect to /NoAuth/Login.html for base URL");
+    ok($agent->current_form->find_input('user'));
+    ok($agent->current_form->find_input('pass'));
+    like($agent->current_form->action, qr{/NoAuth/Login\.html$}, "login form action is correct");
+
+    ok($agent->content =~ /username:/i);
+    $agent->field( 'user' => 'root' );
+    $agent->field( 'pass' => 'wrongpass' );
+
+    # the field isn't named, so we have to click link 0
+    $agent->click(0);
+    is( $agent->status, 200, "Fetched the page ok");
+
+    ok( $agent->content =~ /Your username or password is incorrect/i, "Found the error message");
+    like( $agent->uri, qr{/NoAuth/Login\.html$}, "now on /NoAuth/Login.html" );
+    $agent->logout();
+
+    # Handle the warning after we're done with the page, since this leaves us
+    # with a completely different $mech
+    $agent->warning_like(qr/FAILED LOGIN for root/, "got failed login warning");
+}
+
+# test a login from a non-front page, both with a double leading slash and without
+for my $path (qw(Prefs/Other.html /Prefs/Other.html)) {
+    my $requested = $url.$path;
+    $agent->get_ok($requested);
+    is($agent->status, 200, "Loaded a page");
+    like($agent->uri, qr'/NoAuth/Login\.html\?next=[a-z0-9]{32}', "on login page, with next page hash");
+    is($agent->{redirected_uri}, $requested, "redirected from our requested page");
+
+    ok($agent->current_form->find_input('user'));
+    ok($agent->current_form->find_input('pass'));
+    ok($agent->current_form->find_input('next'));
+    like($agent->value('next'), qr/^[a-z0-9]{32}$/i, "next page argument is a hash");
+    like($agent->current_form->action, qr{/NoAuth/Login\.html$}, "login form action is correct");
+
+    ok($agent->content =~ /username:/i);
+    $agent->field( 'user' => 'root' );
+    $agent->field( 'pass' => 'password' );
+
+    # the field isn't named, so we have to click link 0
+    $agent->click(0);
+    is( $agent->status, 200, "Fetched the page ok");
+    ok( $agent->content =~ /Logout/i, "Found a logout link");
+
+    if ($path =~ m{/}) {
+        (my $collapsed = $path) =~ s{^/}{};
+        is( $agent->uri, $url.$collapsed, "right URL, with leading slashes in path collapsed" );
+    } else {
+        is( $agent->uri, $requested, "right URL" );
+    }
+
+    like( $agent->{redirected_uri}, qr{/NoAuth/Login\.html}, "We redirected from login");
+    $agent->logout();
+}
+
+# test a bogus login from a non-front page
+{
+    my $requested = $url.'Prefs/Other.html';
+    $agent->get_ok($requested);
+    is($agent->status, 200, "Loaded a page");
+    like($agent->uri, qr'/NoAuth/Login\.html\?next=[a-z0-9]{32}', "on login page, with next page hash");
+    is($agent->{redirected_uri}, $requested, "redirected from our requested page");
+
+    ok($agent->current_form->find_input('user'));
+    ok($agent->current_form->find_input('pass'));
+    ok($agent->current_form->find_input('next'));
+    like($agent->value('next'), qr/^[a-z0-9]{32}$/i, "next page argument is a hash");
+    like($agent->current_form->action, qr{/NoAuth/Login\.html$}, "login form action is correct");
+
+    ok($agent->content =~ /username:/i);
+    $agent->field( 'user' => 'root' );
+    $agent->field( 'pass' => 'wrongpass' );
+
+    # the field isn't named, so we have to click link 0
+    $agent->click(0);
+    is( $agent->status, 200, "Fetched the page ok");
+
+    ok( $agent->content =~ /Your username or password is incorrect/i, "Found the error message");
+    like( $agent->uri, qr{/NoAuth/Login\.html$}, "still on /NoAuth/Login.html" );
+
+    # try to login again
+    ok($agent->current_form->find_input('user'));
+    ok($agent->current_form->find_input('pass'));
+    ok($agent->current_form->find_input('next'));
+    like($agent->value('next'), qr/^[a-z0-9]{32}$/i, "next page argument is a hash");
+    like($agent->current_form->action, qr{/NoAuth/Login\.html$}, "login form action is correct");
+
+    ok($agent->content =~ /username:/i);
+    $agent->field( 'user' => 'root' );
+    $agent->field( 'pass' => 'password' );
+
+    # the field isn't named, so we have to click link 0
+    $agent->click(0);
+    is( $agent->status, 200, "Fetched the page ok");
+
+    # check out where we got to
+    is( $agent->uri, $requested, "right URL" );
+    like( $agent->{redirected_uri}, qr{/NoAuth/Login\.html}, "We redirected from login");
+    $agent->logout();
+
+    # Handle the warning after we're done with the page, since this leaves us
+    # with a completely different $mech
+    $agent->warning_like(qr/FAILED LOGIN for root/, "got failed login warning");
+}
+
+# test a login from the main page with query params
+{
+    my $requested = $url."?user=root;pass=password";
+    $agent->get_ok($requested);
+    is($agent->{'status'}, 200, "Loaded a page");
+    is($agent->uri, $requested, "didn't redirect to /NoAuth/Login.html for base URL");
+    ok($agent->content =~ /Logout/i, "Found a logout link - we're logged in");
+    $agent->logout();
+}
+
+# test a bogus login from the main page with query params
+{
+    my $requested = $url."?user=root;pass=wrongpass";
+    $agent->get_ok($requested);
+    is($agent->{'status'}, 200, "Loaded a page");
+    is($agent->uri, $requested, "didn't redirect to /NoAuth/Login.html for base URL");
+    
+    ok($agent->content =~ /Your username or password is incorrect/i, "Found the error message");
+    ok($agent->current_form->find_input('user'));
+    ok($agent->current_form->find_input('pass'));
+    like($agent->current_form->action, qr{/NoAuth/Login\.html$}, "login form action is correct");
+    
+    # Handle the warning after we're done with the page, since this leaves us
+    # with a completely different $mech
+    $agent->warning_like(qr/FAILED LOGIN for root/, "got failed login warning");
+}
+
+# test a bogus login from a non-front page with query params
+{
+    my $requested = $url."Prefs/Other.html?user=root;pass=wrongpass";
+    $agent->get_ok($requested);
+    is($agent->status, 200, "Loaded a page");
+    like($agent->uri, qr'/NoAuth/Login\.html\?next=[a-z0-9]{32}', "on login page, with next page hash");
+    is($agent->{redirected_uri}, $requested, "redirected from our requested page");
+    ok( $agent->content =~ /Your username or password is incorrect/i, "Found the error message");
+
+    ok($agent->current_form->find_input('user'));
+    ok($agent->current_form->find_input('pass'));
+    ok($agent->current_form->find_input('next'));
+    like($agent->value('next'), qr/^[a-z0-9]{32}$/i, "next page argument is a hash");
+    like($agent->current_form->action, qr{/NoAuth/Login\.html$}, "login form action is correct");
+
+    # Try to login again
+    ok($agent->content =~ /username:/i);
+    $agent->field( 'user' => 'root' );
+    $agent->field( 'pass' => 'password' );
+
+    # the field isn't named, so we have to click link 0
+    $agent->click(0);
+    is( $agent->status, 200, "Fetched the page ok");
+
+    # check out where we got to
+    is( $agent->uri, $requested, "right URL" );
+    like( $agent->{redirected_uri}, qr{/NoAuth/Login\.html}, "We redirected from login");
+    $agent->logout();
+
+    # Handle the warning after we're done with the page, since this leaves us
+    # with a completely different $mech
+    $agent->warning_like(qr/FAILED LOGIN for root/, "got failed login warning");
+}
+
+# test REST login response
+{
+    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();
+}
+
+# test REST login response for wrong pass
+{
+    my $requested = $url."REST/1.0/?user=root;pass=passwrong";
+    $agent->get_ok($requested);
+    is($agent->status, 200, "Loaded a page");
+    is($agent->uri, $requested, "didn't redirect to /NoAuth/Login.html for REST");
+    like($agent->content, qr/401 Credentials required/i, "got error status");
+    like($agent->content, qr/Your username or password is incorrect/, "got error message");
+    
+    # Handle the warning after we're done with the page, since this leaves us
+    # with a completely different $mech
+    $agent->warning_like(qr/FAILED LOGIN for root/, "got failed login warning");
+}
+
+# test REST login response for no creds
+{
+    my $requested = $url."REST/1.0/";
+    $agent->get_ok($requested);
+    is($agent->status, 200, "Loaded a page");
+    is($agent->uri, $requested, "didn't redirect to /NoAuth/Login.html for REST");
+    like($agent->content, qr/401 Credentials required/i, "got error status");
+    unlike($agent->content, qr/Your username or password is incorrect/, "didn't get any error message");
+}
+
+# XXX TODO: we should also be testing WebExternalAuth here, but we don't have
+# the framework for dealing with that
+
+1;

commit d37f3e93f799824e84aa146dba44e5669525cb01
Author: Thomas Sibley <trs at bestpractical.com>
Date:   Mon Oct 25 15:38:57 2010 -0400

    Delete next page hashes from the session as we use them

diff --git a/lib/RT/Interface/Web.pm b/lib/RT/Interface/Web.pm
index c6c006d..4c74b6a 100755
--- a/lib/RT/Interface/Web.pm
+++ b/lib/RT/Interface/Web.pm
@@ -561,7 +561,7 @@ sub AttemptPasswordAuthentication {
 
         # It's important to nab the next page from the session before we blow
         # the session away
-        my $next = $HTML::Mason::Commands::session{'NextPage'}->{$ARGS->{'next'} || ''};
+        my $next = delete $HTML::Mason::Commands::session{'NextPage'}->{$ARGS->{'next'} || ''};
 
         InstantiateNewSession();
         $HTML::Mason::Commands::session{'CurrentUser'} = $user_obj;

commit 280bdc1961def7707392ba828f29a4ea8882163a
Author: Matt Zagrabelny <mzagrabe at d.umn.edu>
Date:   Thu Oct 21 12:19:26 2010 -0500

    Link the Basics title in SelfService/Display.html to the Update page
    
    Conditionally, if user has ModifyTicket right, make 'The Basics' title a
    link in /SelfService/Display.html. Link will be to
    SelfService/Update.html, with the possibility to override using a
    callback.

diff --git a/share/html/SelfService/Display.html b/share/html/SelfService/Display.html
index c796109..df6bd16 100755
--- a/share/html/SelfService/Display.html
+++ b/share/html/SelfService/Display.html
@@ -54,8 +54,9 @@
   <table width="100%" class="ticketsummary" >
       <tr>
         <td valign="top" width="50%" class="boxcontainer">
-          <&| /Widgets/TitleBox, title => loc('The Basics'), 
-                title_class=> 'inverse',  
+          <&| /Widgets/TitleBox, title => loc('The Basics'),
+                ($Ticket->CurrentUserHasRight('ModifyTicket') ? (title_href => $title_box_link) : ()),
+                title_class=> 'inverse',
                 color => "#993333" &>
                 <& /Ticket/Elements/ShowBasics, Ticket => $Ticket &>
                 <& /Ticket/Elements/ShowCustomFields, Ticket => $Ticket &>
@@ -224,7 +225,8 @@ if ( defined ($id[0]) && $id[0] eq 'new' ) {
     my $attachments =
       $m->comp( '/Ticket/Elements/FindAttachments', Ticket => $Ticket );
 
-    $m->callback(CallbackName => 'BeforeDisplay', Ticket => \$Ticket, ARGSRef => \%ARGS);
+    my $title_box_link = RT->Config->Get('WebPath')."/SelfService/Update.html?id=".$Ticket->Id;
+    $m->callback(CallbackName => 'BeforeDisplay', Ticket => \$Ticket, ARGSRef => \%ARGS, title_box_link => \$title_box_link);
 </%INIT>
 
 

commit 10572e20930fb78647124ee3ec30c046d0acf797
Author: Thomas Sibley <trs at bestpractical.com>
Date:   Tue Oct 26 12:10:56 2010 -0400

    Also show The Basics link on SelfService with right ReplyToTicket

diff --git a/share/html/SelfService/Display.html b/share/html/SelfService/Display.html
index df6bd16..5cb278d 100755
--- a/share/html/SelfService/Display.html
+++ b/share/html/SelfService/Display.html
@@ -55,7 +55,7 @@
       <tr>
         <td valign="top" width="50%" class="boxcontainer">
           <&| /Widgets/TitleBox, title => loc('The Basics'),
-                ($Ticket->CurrentUserHasRight('ModifyTicket') ? (title_href => $title_box_link) : ()),
+                ($LinkBasicsTitle ? (title_href => $title_box_link) : ()),
                 title_class=> 'inverse',
                 color => "#993333" &>
                 <& /Ticket/Elements/ShowBasics, Ticket => $Ticket &>
@@ -225,6 +225,8 @@ if ( defined ($id[0]) && $id[0] eq 'new' ) {
     my $attachments =
       $m->comp( '/Ticket/Elements/FindAttachments', Ticket => $Ticket );
 
+    my $LinkBasicsTitle = $Ticket->CurrentUserHasRight('ModifyTicket')
+                          || $Ticket->CurrentUserHasRight('ReplyToTicket');
     my $title_box_link = RT->Config->Get('WebPath')."/SelfService/Update.html?id=".$Ticket->Id;
     $m->callback(CallbackName => 'BeforeDisplay', Ticket => \$Ticket, ARGSRef => \%ARGS, title_box_link => \$title_box_link);
 </%INIT>

commit 432df1da150eff66d7b17b2b0812114e0db9896a
Author: sunnavy <sunnavy at bestpractical.com>
Date:   Wed Oct 27 08:52:02 2010 +0800

    no need to ajax more than once

diff --git a/share/html/NoAuth/js/util.js b/share/html/NoAuth/js/util.js
index 908b72b..d835a7f 100644
--- a/share/html/NoAuth/js/util.js
+++ b/share/html/NoAuth/js/util.js
@@ -318,23 +318,13 @@ function checkboxToInput(target,checkbox,val){
 function toggleTicketBookmark( id, url ) {
     var elements = $$("span.toggle-"+id);
     if ( elements.length ) {
-        if ( elements.length == 1 ) {
-            new Ajax.Request(url, {
-                onSuccess: function(response) {
-                    $(elements[0]).replace(response.responseText);
-                }
-                }
-            );
-        }
-        else {
-            new Ajax.Request(url);
-            new Ajax.Request(url+'&Toggle=0', {
-                onSuccess: function(response) {
-                    elements.each( function( item ) {
-                        item.replace(response.responseText);
-                    })
-                }
-            });
-        }
+        new Ajax.Request(url, {
+            method: 'get',
+            onSuccess: function(response) {
+                elements.each( function( item ) {
+                    item.replace(response.responseText);
+                })
+            }
+        });
     }
 }

commit 057552287159e801535e59b8fbd5bd98d1322069
Merge: 432df1d d37f3e9
Author: Thomas Sibley <trs at bestpractical.com>
Date:   Wed Oct 27 10:42:35 2010 -0400

    Merge branch 'redirect-after-login' into 3.8-trunk


commit 7d9ba2a3e3b2e774ab5f91241d91aaa0a277e957
Author: Jesse Vincent <jesse at bestpractical.com>
Date:   Tue Oct 26 17:19:03 2010 -0400

    Add Transaction type to CSS classes in Ticket history for easier local
    theming

diff --git a/share/html/Ticket/Elements/ShowTransaction b/share/html/Ticket/Elements/ShowTransaction
index 9230595..53756ff 100755
--- a/share/html/Ticket/Elements/ShowTransaction
+++ b/share/html/Ticket/Elements/ShowTransaction
@@ -45,7 +45,7 @@
 %# those contributions and any derivatives thereof.
 %# 
 %# END BPS TAGGED BLOCK }}}
-<div class="ticket-transaction <% $type_class %> <% $RowNum % 2 ? 'odd' : 'even' %>">
+<div class="ticket-transaction <% $type_class %> <%$type%> <% $RowNum % 2 ? 'odd' : 'even' %>">
 % $m->callback( titlebar_cmd => \$titlebar_commands, Transaction => $Transaction, %ARGS, CallbackName => 'ModifyDisplay' );
 
 <div class="ticket-transaction">

commit fb836bf4302a7450beaa1e3d36dc0509fe2119c0
Author: Jesse Vincent <jesse at bestpractical.com>
Date:   Tue Oct 19 16:06:48 2010 +0900

    Postgres 9 changed their default representation of BYTEA values. RT
    expects the older "Escape" version. Use that instead.

diff --git a/lib/RT/Handle.pm b/lib/RT/Handle.pm
index da7a672..18f64a0 100755
--- a/lib/RT/Handle.pm
+++ b/lib/RT/Handle.pm
@@ -119,6 +119,15 @@ sub Connect {
         $self->dbh->do("SET NAMES 'utf8'") if $version >= 4.1;
     }
 
+
+    if ( $db_type eq 'Pg' ) {
+        my $version = $self->DatabaseVersion;
+        ($version) = $version =~ /^(\d+\.\d+)/;
+        $self->dbh->do("SET bytea_output = 'escape'") if $version >= 9.0;
+    }
+
+
+
     $self->dbh->{'LongReadLen'} = RT->Config->Get('MaxAttachmentSize');
 }
 

commit e4e90f46bee6ff3ce1263ae78cb4b8e01e19ea23
Author: Kevin Falcone <falcone at bestpractical.com>
Date:   Thu Nov 11 15:52:32 2010 -0500

    This prevents you from creating Transaction Custom Fields
    
    It is quite valid to list queues and create a Transaction Custom Field
    you want applied to those queues.

diff --git a/lib/RT/Handle.pm b/lib/RT/Handle.pm
index 18f64a0..f9b04ba 100755
--- a/lib/RT/Handle.pm
+++ b/lib/RT/Handle.pm
@@ -835,7 +835,7 @@ sub InsertData {
             my @queues;
             # if ref then it's list of queues, so we do things ourself
             if ( exists $item->{'Queue'} && ref $item->{'Queue'} ) {
-                $item->{'LookupType'} = 'RT::Queue-RT::Ticket';
+                $item->{'LookupType'} ||= 'RT::Queue-RT::Ticket';
                 @queues = @{ delete $item->{'Queue'} };
             }
 

commit c252120977932e4d6166e4c4b019cdd87295ef84
Author: Kevin Falcone <falcone at bestpractical.com>
Date:   Fri Nov 12 14:48:10 2010 -0500

    Show errors inline like EditCustomFields

diff --git a/share/html/Ticket/Elements/EditTransactionCustomFields b/share/html/Ticket/Elements/EditTransactionCustomFields
index f79ef1d..ac1a0f2 100644
--- a/share/html/Ticket/Elements/EditTransactionCustomFields
+++ b/share/html/Ticket/Elements/EditTransactionCustomFields
@@ -57,6 +57,10 @@
     NamePrefix => $NamePrefix
 &>
 <em><% $CF->FriendlyType %></em>
+%  if (my $msg = $m->notes('InvalidField-' . $CF->Id)) {
+        <br />
+        <span class="cfinvalidfield"><% $msg %></span>
+%  }
 </td>
 </td></tr>
 % }

commit fe9ac7a0523e5a942dc6016b660e0e06fb52d796
Author: Matt Zagrabelny <mzagrabe at d.umn.edu>
Date:   Fri Nov 12 15:40:00 2010 -0500

    Add a callback after the requestor textbox in Ticket/Create

diff --git a/share/html/Ticket/Create.html b/share/html/Ticket/Create.html
index 2bc6ea0..81ec2bd 100755
--- a/share/html/Ticket/Create.html
+++ b/share/html/Ticket/Create.html
@@ -86,6 +86,7 @@
 </td>
 <td class="value" colspan="5">
 <& /Elements/EmailInput, Name => 'Requestors', Size => '40', Default => $ARGS{Requestors} || $session{CurrentUser}->EmailAddress &>
+% $m->callback( CallbackName => 'AfterRequestors', QueueObj => $QueueObj, ARGSRef => \%ARGS );
 </td>
 </tr>
 <tr>

commit 5f5935fc820252672f798fc285bff45d5068ca0f
Author: Kevin Falcone <falcone at bestpractical.com>
Date:   Thu Nov 4 14:51:56 2010 -0400

    Ensure that signatures are loaded on Jumbo
    
    A change to maintain Message Content on page reload caused signatures to
    never be loaded.

diff --git a/share/html/Ticket/ModifyAll.html b/share/html/Ticket/ModifyAll.html
index 3fe2871..4a80716 100755
--- a/share/html/Ticket/ModifyAll.html
+++ b/share/html/Ticket/ModifyAll.html
@@ -135,7 +135,7 @@
   <tr>
     <td class="labeltop"><&|/l&>Content</&>:</td>
     <td class="entry">
-% if (exists $ARGS{UpdateContent}) {
+% if (defined $ARGS{UpdateContent} && length($ARGS{UpdateContent})) {
     <& /Elements/MessageBox, Name=>"UpdateContent", Default=>$ARGS{UpdateContent}, IncludeSignature => 0 &>
 % } else {
     <& /Elements/MessageBox, Name=>"UpdateContent", QuoteTransaction=>$ARGS{QuoteTransaction} &>

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


More information about the Rt-commit mailing list