import rt 3.6.4
[freeside.git] / rt / html / autohandler
index ce8b756..823adef 100644 (file)
@@ -1,8 +1,14 @@
-%# BEGIN LICENSE BLOCK
+%# BEGIN BPS TAGGED BLOCK {{{
 %# 
-%# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+%# COPYRIGHT:
+%#  
+%# This software is Copyright (c) 1996-2007 Best Practical Solutions, LLC 
+%#                                          <jesse@bestpractical.com>
 %# 
-%# (Except where explictly superceded by other copyright notices)
+%# (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
 %# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 %# General Public License for more details.
 %# 
-%# Unless otherwise specified, all modifications, corrections or
-%# extensions to this work which alter its source code become the
-%# property of Best Practical Solutions, LLC when submitted for
-%# inclusion in the work.
+%# 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/copyleft/gpl.html.
+%# 
 %# 
+%# CONTRIBUTION SUBMISSION POLICY:
 %# 
-%# END LICENSE BLOCK
+%# (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 }}}
 <%INIT>
 
 # Roll back any dangling transactions from a previous failed connection
 $RT::Handle->ForceRollback() if $RT::Handle->TransactionDepth;
 
 
-local *session;
+if ($RT::StatementLog) {
+    $RT::Handle->ClearSQLStatementLog;
+    $RT::Handle->LogSQLStatements(1);
+}
+
+local *session
+    unless $m->is_subrequest;    # avoid reentrancy, as suggested by masonbook
+
+# Disable AutoFlush using an attribute
+if ( $m->request_comp->attr_exists('AutoFlush') ) {
+    $m->autoflush( $m->request_comp->attr('AutoFlush') );
+}
+
 %ARGS = map {
-    # if they've passed multiple values, they'll be an array. if they've passed just one, a scalar
-    # whatever they are, mark them as utf8
+
+    # if they've passed multiple values, they'll be an array. if they've
+    # passed just one, a scalar whatever they are, mark them as utf8
     my $type = ref($_);
-    (!$type)
-       ? Encode::decode(utf8 => $_, Encode::FB_PERLQQ) :
-    ($type eq 'ARRAY')
-       ? [ map { ref($_) ? $_ : Encode::decode(utf8 => $_, Encode::FB_PERLQQ) } @$_ ] :
-    ($type eq 'HASH')
-       ? { map { ref($_) ? $_ : Encode::decode(utf8 => $_, Encode::FB_PERLQQ) } %$_ } : $_
+    ( !$type )
+        ? Encode::is_utf8($_)
+        ? $_
+        : Encode::decode( utf8 => $_, Encode::FB_PERLQQ )
+        : ( $type eq 'ARRAY' )
+        ? [
+        map {
+            ( ref($_) or Encode::is_utf8($_) )
+                ? $_
+                : Encode::decode( utf8 => $_, Encode::FB_PERLQQ )
+            } @$_
+        ]
+        : ( $type eq 'HASH' )
+        ? {
+        map {
+            ( ref($_) or Encode::is_utf8($_) )
+                ? $_
+                : Encode::decode( utf8 => $_, Encode::FB_PERLQQ )
+            } %$_
+        }
+        : $_
 } %ARGS;
 
-if ($ARGS{'Debug'}) {
-        require Time::HiRes;
-        $m->{'rt_base_time'} = [Time::HiRes::gettimeofday()];
-        
-}
-else {
-        $m->{'rt_base_time'} = time;
+# Latter in the code we use
+# $m->comp( { base_comp => $m->request_comp }, $m->fetch_next, %ARGS );
+# instead of $m->call_next to avoid problems with UTF8 keys in arguments.
+# The call_next method pass through original arguments and if you have
+# an argument with unicode key then in a next component you'll get two
+# records in the args hash: one with key without UTF8 flag and another
+# with the flag, which may result into errors. "{ base_comp => $m->request_comp }"
+# is copied from mason's source to get the same results as we get from
+# call_next method, this feature is not documented, so we just leave it
+# here to avoid possible side effects.
+
+# This code canonicalizes time inputs in hours into minutes
+foreach my $field ( keys %ARGS ) {
+    next unless $field =~ /^(.*)-TimeUnits$/i && $ARGS{$1};
+    my $local = $1;
+    $ARGS{$local} =~ s{\b (?: (\d+) \s+ )? (\d+)/(\d+) \b}
+                      {($1 || 0) + $3 ? $2 / $3 : 0}xe;
+    if ( $ARGS{$field} && $ARGS{$field} =~ /hours/i ) {
+        $ARGS{$local} *= 60;
+    }
+    delete $ARGS{$field};
 }
-$m->comp('/Elements/SetupSessionCookie', %ARGS);
 
-unless ($session{'CurrentUser'} && $session{'CurrentUser'}->Id) {
+$m->{'rt_base_time'} = [ Time::HiRes::gettimeofday() ];
+
+$m->comp( '/Elements/SetupSessionCookie', %ARGS );
+
+unless ( $session{'CurrentUser'} && $session{'CurrentUser'}->Id ) {
     $session{'CurrentUser'} = RT::CurrentUser->new();
 }
 
@@ -58,117 +127,201 @@ unless ($session{'CurrentUser'} && $session{'CurrentUser'}->Id) {
 $r->content_type("text/html; charset=utf-8");
 
 # If it's a noauth file, don't ask for auth.
-if ($m->base_comp->path =~ '^/+NoAuth/' ||
-    $m->base_comp->path =~ '^/+REST/\d+\.\d+/NoAuth/')
-{
-    $m->call_next(%ARGS);
-    $m->abort();
+if ( $m->base_comp->path =~ $RT::WebNoAuthRegex ) {
+    $m->comp( { base_comp => $m->request_comp }, $m->fetch_next, %ARGS);
+    $m->abort;
 }
 
-# If RT is configured for external auth, let's get REMOTE_USER
-elsif ($RT::WebExternalAuth and length($ENV{'REMOTE_USER'})) {
-    my $orig_user = $user;
+# If RT is configured for external auth, let's go through and get REMOTE_USER
+elsif ($RT::WebExternalAuth) {
 
-    $user = $ENV{'REMOTE_USER'};
-    $session{'CurrentUser'} = RT::CurrentUser->new();
-    my $load_method = $RT::WebExternalGecos ? 'LoadByGecos' : 'Load';
-    
-    if ($^O eq 'MSWin32' and $RT::WebExternalGecos) {
-       my $NodeName = Win32::NodeName();
-       $user =~ s/^\Q$NodeName\E\\//i;
-    }
+    # do we actually have a REMOTE_USER equivlent?
+    if ( RT::Interface::Web::WebCanonicalizeInfo() ) {
 
-    $session{'CurrentUser'}->$load_method($user);
+        my $orig_user = $user;
 
-    if ($RT::WebExternalAuto and !$session{'CurrentUser'}->Id() ) {
-       # Create users on-the-fly with default attributes
+        $user = RT::Interface::Web::WebCanonicalizeInfo();
+        $session{'CurrentUser'} = RT::CurrentUser->new();
+        my $load_method = $RT::WebExternalGecos ? 'LoadByGecos' : 'Load';
 
-       my $UserObj = RT::User->new(RT::CurrentUser->new('root'));
+        if ( $^O eq 'MSWin32' and $RT::WebExternalGecos ) {
+            my $NodeName = Win32::NodeName();
+            $user =~ s/^\Q$NodeName\E\\//i;
+        }
 
-       my ($val, $msg) = $UserObj->Create(
-           %{ref($RT::AutoCreate) ? $RT::AutoCreate : {}},
-           Name         => $user,
-           Gecos        => $user,
-       );
+        $session{'CurrentUser'}->$load_method($user);
 
-       if ($val) {
-           $UserObj->SetPrivileged(1);
+        if ( $RT::WebExternalAuto and !$session{'CurrentUser'}->Id() ) {
 
-           if ($^O !~ /^(?:riscos|MacOS|MSWin32|dos|os2)$/) {
-               # Populate fields with information from Unix /etc/passwd
+            # Create users on-the-fly
 
-               my ($comments, $realname) = (getpwnam($user))[5, 6];
-               $UserObj->SetComments($comments) if defined $comments;
-               $UserObj->SetRealName($realname) if defined $realname;
-           }
-           elsif ($^O eq 'MSWin32' and eval 'use Net::AdminMisc; 1') {
-               # Populate fields with information from NT domain controller
-           }
+            my $UserObj = RT::User->new( RT::CurrentUser->new('RT_System') );
 
-           $session{'CurrentUser'}->Load($user);
-       }
-       else {
-           delete $session{'CurrentUser'};
-           $m->abort() unless $RT::WebFallbackToInternalAuth;
-           $m->comp('/Elements/Login', %ARGS, Error=> loc('Cannot create user: [_1]', $msg));
-       }
-    }
+            my ( $val, $msg ) = $UserObj->Create(
+                %{ ref($RT::AutoCreate) ? $RT::AutoCreate : {} },
+                Name  => $user,
+                Gecos => $user,
+            );
 
-    unless ( $session{'CurrentUser'}->Id() ) {
-        delete $session{'CurrentUser'};
-        $user = $orig_user;
+            if ($val) {
+
+               # now get user specific information, to better create our user.
+                my $new_user_info
+                    = RT::Interface::Web::WebExternalAutoInfo($user);
+
+        # set the attributes that have been defined.
+        # FIXME: this is a horrible kludge. I'm sure there's something cleaner
+                foreach my $attribute (
+                    'Name',                  'Comments',
+                    'Signature',             'EmailAddress',
+                    'PagerEmailAddress',     'FreeformContactInfo',
+                    'Organization',          'Disabled',
+                    'Privileged',            'RealName',
+                    'NickName',              'Lang',
+                    'EmailEncoding',         'WebEncoding',
+                    'ExternalContactInfoId', 'ContactInfoSystem',
+                    'ExternalAuthId',        'Gecos',
+                    'HomePhone',             'WorkPhone',
+                    'MobilePhone',           'PagerPhone',
+                    'Address1',              'Address2',
+                    'City',                  'State',
+                    'Zip',                   'Country'
+                    )
+                {
+                    $m->comp( '/Elements/Callback', %ARGS,
+                        _CallbackName => 'NewUser' );
+
+                    my $method = "Set$attribute";
+                    $UserObj->$method( $new_user_info->{$attribute} )
+                        if ( defined $new_user_info->{$attribute} );
+                }
+                $session{'CurrentUser'}->Load($user);
+            }
+            else {
+
+               # we failed to successfully create the user. abort abort abort.
+                delete $session{'CurrentUser'};
+                $m->abort() unless $RT::WebFallbackToInternalAuth;
+                $m->comp( '/Elements/Login', %ARGS,
+                    Error => loc( 'Cannot create user: [_1]', $msg ) );
+            }
+        }
+
+        unless ( $session{'CurrentUser'}->Id() ) {
+            delete $session{'CurrentUser'};
+            $user = $orig_user;
 
-       if ( $RT::WebExternalOnly ) {           
-           $m->comp('/Elements/Login', %ARGS, Error=> loc('You are not an authorized user'));
-           $m->abort();
-       }
+            if ($RT::WebExternalOnly) {
+                $m->comp( '/Elements/Login', %ARGS,
+                    Error => loc('You are not an authorized user') );
+                $m->abort();
+            }
+        }
+    }
+    elsif ($RT::WebFallbackToInternalAuth) {
+        unless ( defined( $session{'CurrentUser'} ) ) {
+            $m->comp( '/Elements/Login', %ARGS,
+                Error => loc('You are not an authorized user') );
+            $m->abort();
+        }
+    }
+    else {
+
+        # WebExternalAuth is set, but we don't have a REMOTE_USER. abort
+        delete $session{'CurrentUser'} if defined $session{'CurrentUser'};
     }
 }
 
 delete $session{'CurrentUser'}
-    unless $session{'CurrentUser'} and defined $session{'CurrentUser'}->Id;
+    unless $session{'CurrentUser'}
+    and $session{'CurrentUser'}->Id;
 
 # Process per-page authentication callbacks
-$m->comp('/Elements/Callback', %ARGS, _CallbackName => 'Auth');
+$m->comp( '/Elements/Callback', %ARGS, _CallbackName => 'Auth' );
 
 # If the user is logging in, let's authenticate
-if (!$session{'CurrentUser'} && defined ($user) && defined ($pass) ){
+if ( !$session{'CurrentUser'} && defined $user && defined $pass ) {
     $session{'CurrentUser'} = RT::CurrentUser->new();
     $session{'CurrentUser'}->Load($user);
 
-    if (!$session{'CurrentUser'}->id() ||
-        !$session{'CurrentUser'}->IsPassword($pass))
+    unless ( $session{'CurrentUser'}->id
+        && $session{'CurrentUser'}->IsPassword($pass) )
     {
         delete $session{'CurrentUser'};
-        $m->comp('/Elements/Login', %ARGS,
-                 Error => loc('Your username or password is incorrect'));
-        $m->abort();
+        $RT::Logger->error("FAILED LOGIN for $user from $ENV{'REMOTE_ADDR'}");
+        $m->comp( '/Elements/Login', %ARGS,
+            Error => loc('Your username or password is incorrect') );
+        $m->comp( '/Elements/Callback', %ARGS, _CallbackName => 'FailedLogin' );
+        $m->abort;
+    }
+    else {
+        $RT::Logger->info(
+            "Successful login for $user from $ENV{'REMOTE_ADDR'}");
+        $m->comp( '/Elements/Callback', %ARGS, _CallbackName => 'SuccessfulLogin' );
     }
 }
-  
+
 # If we've got credentials, let's serve the file up.
-if ( (defined $session{'CurrentUser'}) and 
-     ( $session{'CurrentUser'}->Id) ) {
-    
+if (    ( defined $session{'CurrentUser'} )
+    and ( $session{'CurrentUser'}->Id ) )
+{
+
     # Process per-page global callbacks
-    $m->comp('/Elements/Callback', %ARGS);
+    $m->comp( '/Elements/Callback', %ARGS );
 
     # If the user isn't privileged, they can only see SelfService
-    if ((! $session{'CurrentUser'}->Privileged) and
-       ($m->base_comp->path !~ '^(/+)SelfService/') ) {
-       $m->comp('/SelfService/index.html');
-       $m->abort();
+    if ( not $session{'CurrentUser'}->Privileged ) {
+
+        # if the user is trying to access a ticket, redirect them
+        if (    $m->request_comp->path =~ '^(/+)Ticket/Display.html'
+            and $ARGS{'id'} )
+        {
+            RT::Interface::Web::Redirect($RT::WebURL."SelfService/Display.html?id=".$ARGS{'id'});
+        }
+
+        # otherwise, drop the user at the SelfService default page
+        elsif ( $m->base_comp->path !~ '^(/+)SelfService/' ) {
+            RT::Interface::Web::Redirect($RT::WebURL."SelfService/");
+        }
+        else {
+            $m->comp( { base_comp => $m->request_comp }, $m->fetch_next, %ARGS);
+        }
     }
     else {
-       $m->call_next(%ARGS);
+        $m->comp( { base_comp => $m->request_comp }, $m->fetch_next, %ARGS);
     }
 }
 
 # If we have no credentials
 else {
-    $m->comp('/Elements/Login', %ARGS);
+    $m->comp( '/Elements/Login', %ARGS );
     $m->abort();
 }
+
+if ($RT::StatementLog) {
+    my @log = $RT::Handle->SQLStatementLog;
+    $RT::Handle->ClearSQLStatementLog;
+    for my $stmt (@log) {
+        my ( $time, $sql, $bind, $duration ) = @{$stmt};
+        my @bind;
+        if ( ref $bind ) {
+            @bind = @{$bind};
+        }
+        else {
+
+            # Older DBIx-SB
+            $duration = $bind;
+        }
+        $RT::Logger->log(
+            level   => $RT::StatementLog,
+            message => "SQL(" . sprintf( "%.2f", $duration ) . "s): $sql;"
+                . (
+                @bind ? "  [ bound values: @{[map{qq|'$_'|} @bind]} ]" : ""
+                )
+        );
+    }
+}
+
 </%INIT>
 <& /Elements/Footer, %ARGS &>
 <%ARGS>