X-Git-Url: http://git.freeside.biz/gitweb/?a=blobdiff_plain;f=rt%2Flib%2FRT%2FInterface%2FWeb.pm;h=4e4611bdb2a649b00f79d1f10bf132144a9bcdba;hb=90edd8a914fd484e649fb0aa051dce7927bd6881;hp=106209d64513357f1012f50c1df57c82762484a0;hpb=c587b5fdc7175c2a752558efccfc3f424cff6c0d;p=freeside.git diff --git a/rt/lib/RT/Interface/Web.pm b/rt/lib/RT/Interface/Web.pm index 106209d64..4e4611bdb 100644 --- a/rt/lib/RT/Interface/Web.pm +++ b/rt/lib/RT/Interface/Web.pm @@ -2,8 +2,8 @@ # # COPYRIGHT: # -# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC -# +# This software is Copyright (c) 1996-2011 Best Practical Solutions, LLC +# # # (Except where explicitly superseded by other copyright notices) # @@ -192,6 +192,9 @@ sub HandleRequest { SendSessionCookie(); $HTML::Mason::Commands::session{'CurrentUser'} = RT::CurrentUser->new() unless _UserLoggedIn(); + # Process session-related callbacks before any auth attempts + $HTML::Mason::Commands::m->callback( %$ARGS, CallbackName => 'Session', CallbackPage => '/autohandler' ); + MaybeShowNoAuthPage($ARGS); AttemptExternalAuth($ARGS) if RT->Config->Get('WebExternalAuthContinuous') or not _UserLoggedIn(); @@ -204,13 +207,29 @@ 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; + + # 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, actions => [$msg]); + $m->abort; + } + else { + TangentForLogin(results => ($msg ? LoginError($msg) : undef)); + } } } @@ -223,6 +242,9 @@ sub HandleRequest { ShowRequestedPage($ARGS); LogRecordedSQLStatements(); + + # Process per-page final cleanup callbacks + $HTML::Mason::Commands::m->callback( %$ARGS, CallbackName => 'Final', CallbackPage => '/autohandler' ); } sub _ForceLogout { @@ -239,6 +261,108 @@ 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. Returns +the hash value. + +=cut + +sub SetNextPage { + my $next = shift || IntuitNextPage(); + my $hash = Digest::MD5::md5_hex($next . $$ . rand(1024)); + + $HTML::Mason::Commands::session{'NextPage'}->{$hash} = $next; + $HTML::Mason::Commands::session{'i'}++; + + SendSessionCookie(); + return $hash; +} + + +=head2 TangentForLogin [HASH] + +Redirects to C, setting the value of L 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 TangentForLoginWithError ERROR + +Localizes the passed error message, stashes it with L and then +calls L 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 +tangent. The current request URL is used, or failing that, the C +configuration variable. + +=cut + +sub IntuitNextPage { + my $req_uri; + + # This includes any query parameters. Redirect will take care of making + # it an absolute URL. + 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'); + + # 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 @@ -278,6 +402,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 ); @@ -380,9 +508,12 @@ 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')) { + TangentForLoginWithError('Cannot create user: [_1]', $msg); + } else { + $m->abort(); + } } } @@ -393,15 +524,13 @@ 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(); + 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) - $m->comp( '/Elements/Login', %$ARGS, Error => HTML::Mason::Commands::loc('You are not an authorized user') ); - $m->abort(); + TangentForLoginWithError('You are not an authorized user'); } } else { @@ -414,7 +543,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} ); @@ -422,15 +553,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 = delete $HTML::Mason::Commands::session{'NextPage'}->{$ARGS->{'next'} || ''}; + + InstantiateNewSession(); + $HTML::Mason::Commands::session{'CurrentUser'} = $user_obj; + SendSessionCookie(); + + $m->callback( %$ARGS, CallbackName => 'SuccessfulLogin', CallbackPage => '/autohandler' ); - $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' ); + # 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 @@ -497,6 +647,13 @@ 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; + 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, @@ -1177,13 +1334,22 @@ sub ProcessUpdateMessage { my $bcc = $args{ARGSRef}->{'UpdateBcc'}; my $cc = $args{ARGSRef}->{'UpdateCc'}; + my %txn_customfields; + + foreach my $key ( keys %{ $args{ARGSRef} } ) { + if ( $key =~ /^(?:Object-RT::Transaction--)?CustomField-(\d+)/ ) { + $txn_customfields{$key} = $args{ARGSRef}->{$key}; + } + } + my %message_args = ( CcMessageTo => $cc, BccMessageTo => $bcc, Sign => $args{ARGSRef}->{'Sign'}, Encrypt => $args{ARGSRef}->{'Encrypt'}, MIMEObj => $Message, - TimeTaken => $args{ARGSRef}->{'UpdateTimeWorked'} + TimeTaken => $args{ARGSRef}->{'UpdateTimeWorked'}, + CustomFields => \%txn_customfields, ); my @temp_squelch; @@ -1219,14 +1385,17 @@ sub ProcessUpdateMessage { } my @results; + # Do the update via the appropriate Ticket method if ( $args{ARGSRef}->{'UpdateType'} =~ /^(private|public)$/ ) { - my ( $Transaction, $Description, $Object ) = $args{TicketObj}->Comment(%message_args); + my ( $Transaction, $Description, $Object ) = + $args{TicketObj}->Comment(%message_args); push( @results, $Description ); - $Object->UpdateCustomFields( ARGSRef => $args{ARGSRef} ) if $Object; + #$Object->UpdateCustomFields( ARGSRef => $args{ARGSRef} ) if $Object; } elsif ( $args{ARGSRef}->{'UpdateType'} eq 'response' ) { - my ( $Transaction, $Description, $Object ) = $args{TicketObj}->Correspond(%message_args); + my ( $Transaction, $Description, $Object ) = + $args{TicketObj}->Correspond(%message_args); push( @results, $Description ); - $Object->UpdateCustomFields( ARGSRef => $args{ARGSRef} ) if $Object; + #$Object->UpdateCustomFields( ARGSRef => $args{ARGSRef} ) if $Object; } else { push( @results, loc("Update type was neither correspondence nor comment.") . " " . loc("Update not recorded.") ); @@ -1559,6 +1728,8 @@ sub ProcessTicketCustomFieldUpdates { $ARGSRef->{"Object-RT::Ticket-$1"} = delete $ARGSRef->{$arg}; } elsif ( $arg =~ /^CustomField-(\d+-.*)/ ) { $ARGSRef->{"Object-RT::Ticket--$1"} = delete $ARGSRef->{$arg}; + } elsif ( $arg =~ /^Object-RT::Transaction-(\d*)-CustomField/ ) { + delete $ARGSRef->{$arg}; # don't try to update transaction fields } }