~ubuntu-branches/ubuntu/oneiric/request-tracker3.8/oneiric-updates

« back to all changes in this revision

Viewing changes to lib/RT/Interface/Web.pm

  • Committer: Bazaar Package Importer
  • Author(s): Dominic Hargreaves
  • Date: 2011-04-14 18:37:55 UTC
  • mfrom: (7.1.10 sid)
  • Revision ID: james.westby@ubuntu.com-20110414183755-fasurkmlcqq0ouky
Tags: 3.8.10-1
* New upstream release; includes multiple security fixes
  (Closes: #622774):
  - Remote code execution in external custom fields (CVE-2011-1685)
  - Information disclosure via SQL injection (CVE-2011-1686)
  - Information disclosure via search interface (CVE-2011-1687)
  - Information disclosure via directory traversal (CVE-2011-1688)
  - User javascript execution via XSS vulnerability (CVE-2011-1689)
  - Authentication credentials theft (CVE-2011-1690)
* Update Standards-Version (no changes)

Show diffs side-by-side

added added

removed removed

Lines of Context:
2
2
#
3
3
# COPYRIGHT:
4
4
#
5
 
# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
6
 
#                                          <jesse@bestpractical.com>
 
5
# This software is Copyright (c) 1996-2011 Best Practical Solutions, LLC
 
6
#                                          <sales@bestpractical.com>
7
7
#
8
8
# (Except where explicitly superseded by other copyright notices)
9
9
#
192
192
    SendSessionCookie();
193
193
    $HTML::Mason::Commands::session{'CurrentUser'} = RT::CurrentUser->new() unless _UserLoggedIn();
194
194
 
 
195
    # Process session-related callbacks before any auth attempts
 
196
    $HTML::Mason::Commands::m->callback( %$ARGS, CallbackName => 'Session', CallbackPage => '/autohandler' );
 
197
 
 
198
    MaybeRejectPrivateComponentRequest();
 
199
 
195
200
    MaybeShowNoAuthPage($ARGS);
196
201
 
197
202
    AttemptExternalAuth($ARGS) if RT->Config->Get('WebExternalAuthContinuous') or not _UserLoggedIn();
204
209
    unless ( _UserLoggedIn() ) {
205
210
        _ForceLogout();
206
211
 
207
 
        # If the user is logging in, let's authenticate
208
 
        if ( defined $ARGS->{user} && defined $ARGS->{pass} ) {
209
 
            AttemptPasswordAuthentication($ARGS);
210
 
        } else {
211
 
            # if no credentials then show him login page
212
 
            $HTML::Mason::Commands::m->comp( '/Elements/Login', %$ARGS );
213
 
            $HTML::Mason::Commands::m->abort;
 
212
        # Authenticate if the user is trying to login via user/pass query args
 
213
        my ($authed, $msg) = AttemptPasswordAuthentication($ARGS);
 
214
 
 
215
        unless ($authed) {
 
216
            my $m = $HTML::Mason::Commands::m;
 
217
 
 
218
            # REST urls get a special 401 response
 
219
            if ($m->request_comp->path =~ '^/REST/\d+\.\d+/') {
 
220
                $HTML::Mason::Commands::r->content_type("text/plain");
 
221
                $m->error_format("text");
 
222
                $m->out("RT/$RT::VERSION 401 Credentials required\n");
 
223
                $m->out("\n$msg\n") if $msg;
 
224
                $m->abort;
 
225
            }
 
226
            # Specially handle /index.html so that we get a nicer URL
 
227
            elsif ( $m->request_comp->path eq '/index.html' ) {
 
228
                my $next = SetNextPage(RT->Config->Get('WebURL'));
 
229
                $m->comp('/NoAuth/Login.html', next => $next, actions => [$msg]);
 
230
                $m->abort;
 
231
            }
 
232
            else {
 
233
                TangentForLogin(results => ($msg ? LoginError($msg) : undef));
 
234
            }
214
235
        }
215
236
    }
216
237
 
223
244
 
224
245
    ShowRequestedPage($ARGS);
225
246
    LogRecordedSQLStatements();
 
247
 
 
248
    # Process per-page final cleanup callbacks
 
249
    $HTML::Mason::Commands::m->callback( %$ARGS, CallbackName => 'Final', CallbackPage => '/autohandler' );
226
250
}
227
251
 
228
252
sub _ForceLogout {
239
263
 
240
264
}
241
265
 
 
266
=head2 LoginError ERROR
 
267
 
 
268
Pushes a login error into the Actions session store and returns the hash key.
 
269
 
 
270
=cut
 
271
 
 
272
sub LoginError {
 
273
    my $new = shift;
 
274
    my $key = Digest::MD5::md5_hex( rand(1024) );
 
275
    push @{ $HTML::Mason::Commands::session{"Actions"}->{$key} ||= [] }, $new;
 
276
    $HTML::Mason::Commands::session{'i'}++;
 
277
    return $key;
 
278
}
 
279
 
 
280
=head2 SetNextPage [PATH]
 
281
 
 
282
Intuits and stashes the next page in the sesssion hash.  If PATH is
 
283
specified, uses that instead of the value of L<IntuitNextPage()>.  Returns
 
284
the hash value.
 
285
 
 
286
=cut
 
287
 
 
288
sub SetNextPage {
 
289
    my $next = shift || IntuitNextPage();
 
290
    my $hash = Digest::MD5::md5_hex($next . $$ . rand(1024));
 
291
 
 
292
    $HTML::Mason::Commands::session{'NextPage'}->{$hash} = $next;
 
293
    $HTML::Mason::Commands::session{'i'}++;
 
294
    
 
295
    SendSessionCookie();
 
296
    return $hash;
 
297
}
 
298
 
 
299
 
 
300
=head2 TangentForLogin [HASH]
 
301
 
 
302
Redirects to C</NoAuth/Login.html>, setting the value of L<IntuitNextPage> as
 
303
the next page.  Optionally takes a hash which is dumped into query params.
 
304
 
 
305
=cut
 
306
 
 
307
sub TangentForLogin {
 
308
    my $hash  = SetNextPage();
 
309
    my %query = (@_, next => $hash);
 
310
    my $login = RT->Config->Get('WebURL') . 'NoAuth/Login.html?';
 
311
    $login .= $HTML::Mason::Commands::m->comp('/Elements/QueryString', %query);
 
312
    Redirect($login);
 
313
}
 
314
 
 
315
=head2 TangentForLoginWithError ERROR
 
316
 
 
317
Localizes the passed error message, stashes it with L<LoginError> and then
 
318
calls L<TangentForLogin> with the appropriate results key.
 
319
 
 
320
=cut
 
321
 
 
322
sub TangentForLoginWithError {
 
323
    my $key = LoginError(HTML::Mason::Commands::loc(@_));
 
324
    TangentForLogin( results => $key );
 
325
}
 
326
 
 
327
=head2 IntuitNextPage
 
328
 
 
329
Attempt to figure out the path to which we should return the user after a
 
330
tangent.  The current request URL is used, or failing that, the C<WebURL>
 
331
configuration variable.
 
332
 
 
333
=cut
 
334
 
 
335
sub IntuitNextPage {
 
336
    my $req_uri;
 
337
 
 
338
    # This includes any query parameters.  Redirect will take care of making
 
339
    # it an absolute URL.
 
340
    if ($ENV{'REQUEST_URI'}) {
 
341
        $req_uri = $ENV{'REQUEST_URI'};
 
342
 
 
343
        # collapse multiple leading slashes so the first part doesn't look like
 
344
        # a hostname of a schema-less URI
 
345
        $req_uri =~ s{^/+}{/};
 
346
    }
 
347
 
 
348
    my $next = defined $req_uri ? $req_uri : RT->Config->Get('WebURL');
 
349
 
 
350
    # sanitize $next
 
351
    my $uri = URI->new($next);
 
352
 
 
353
    # You get undef scheme with a relative uri like "/Search/Build.html"
 
354
    unless (!defined($uri->scheme) || $uri->scheme eq 'http' || $uri->scheme eq 'https') {
 
355
        $next = RT->Config->Get('WebURL');
 
356
    }
 
357
 
 
358
    # Make sure we're logging in to the same domain
 
359
    # You can get an undef authority with a relative uri like "index.html"
 
360
    my $uri_base_url = URI->new(RT->Config->Get('WebBaseURL'));
 
361
    unless (!defined($uri->authority) || $uri->authority eq $uri_base_url->authority) {
 
362
        $next = RT->Config->Get('WebURL');
 
363
    }
 
364
 
 
365
    return $next;
 
366
}
 
367
 
242
368
=head2 MaybeShowInstallModePage 
243
369
 
244
370
This function, called exclusively by RT's autohandler, dispatches
278
404
 
279
405
    return unless $m->base_comp->path =~ RT->Config->Get('WebNoAuthRegex');
280
406
 
 
407
    # Don't show the login page to logged in users
 
408
    Redirect(RT->Config->Get('WebURL'))
 
409
        if $m->base_comp->path eq '/NoAuth/Login.html' and _UserLoggedIn();
 
410
 
281
411
    # If it's a noauth file, don't ask for auth.
282
412
    SendSessionCookie();
283
413
    $m->comp( { base_comp => $m->request_comp }, $m->fetch_next, %$ARGS );
284
414
    $m->abort;
285
415
}
286
416
 
 
417
=head2 MaybeRejectPrivateComponentRequest
 
418
 
 
419
This function will reject calls to private components, like those under
 
420
C</Elements>. If the requested path is a private component then we will
 
421
abort with a C<403> error.
 
422
 
 
423
=cut
 
424
 
 
425
sub MaybeRejectPrivateComponentRequest {
 
426
    my $m = $HTML::Mason::Commands::m;
 
427
    my $path = $m->request_comp->path;
 
428
 
 
429
    # We do not check for dhandler here, because requesting our dhandlers
 
430
    # directly is okay. Mason will invoke the dhandler with a dhandler_arg of
 
431
    # 'dhandler'.
 
432
 
 
433
    if ($path =~ m{
 
434
            / # leading slash
 
435
            ( Elements    |
 
436
              _elements   | # mobile UI
 
437
              Widgets     |
 
438
              autohandler | # requesting this directly is suspicious
 
439
              l           ) # loc component
 
440
            ( $ | / ) # trailing slash or end of path
 
441
        }xi) {
 
442
            $m->abort(403);
 
443
    }
 
444
 
 
445
    return;
 
446
}
 
447
 
287
448
=head2 ShowRequestedPage  \%ARGS
288
449
 
289
450
This function, called exclusively by RT's autohandler, dispatches
380
541
 
381
542
                # we failed to successfully create the user. abort abort abort.
382
543
                delete $HTML::Mason::Commands::session{'CurrentUser'};
383
 
                $m->comp( '/Elements/Login', %$ARGS, Error => HTML::Mason::Commands::loc( 'Cannot create user: [_1]', $msg ) )
384
 
                    if RT->Config->Get('WebFallbackToInternalAuth');;
385
 
                $m->abort();
 
544
 
 
545
                if (RT->Config->Get('WebFallbackToInternalAuth')) {
 
546
                    TangentForLoginWithError('Cannot create user: [_1]', $msg);
 
547
                } else {
 
548
                    $m->abort();
 
549
                }
386
550
            }
387
551
        }
388
552
 
393
557
            $user = $orig_user;
394
558
 
395
559
            if ( RT->Config->Get('WebExternalOnly') ) {
396
 
                $m->comp( '/Elements/Login', %$ARGS, Error => HTML::Mason::Commands::loc('You are not an authorized user') );
397
 
                $m->abort();
 
560
                TangentForLoginWithError('You are not an authorized user');
398
561
            }
399
562
        }
400
563
    } elsif ( RT->Config->Get('WebFallbackToInternalAuth') ) {
401
564
        unless ( defined $HTML::Mason::Commands::session{'CurrentUser'} ) {
402
565
            # XXX unreachable due to prior defaulting in HandleRequest (check c34d108)
403
 
            $m->comp( '/Elements/Login', %$ARGS, Error => HTML::Mason::Commands::loc('You are not an authorized user') );
404
 
            $m->abort();
 
566
            TangentForLoginWithError('You are not an authorized user');
405
567
        }
406
568
    } else {
407
569
 
414
576
}
415
577
 
416
578
sub AttemptPasswordAuthentication {
417
 
    my $ARGS     = shift;
 
579
    my $ARGS = shift;
 
580
    return unless defined $ARGS->{user} && defined $ARGS->{pass};
 
581
 
418
582
    my $user_obj = RT::CurrentUser->new();
419
583
    $user_obj->Load( $ARGS->{user} );
420
584
 
422
586
 
423
587
    unless ( $user_obj->id && $user_obj->IsPassword( $ARGS->{pass} ) ) {
424
588
        $RT::Logger->error("FAILED LOGIN for @{[$ARGS->{user}]} from $ENV{'REMOTE_ADDR'}");
425
 
        $m->comp( '/Elements/Login', %$ARGS, Error => HTML::Mason::Commands::loc('Your username or password is incorrect'), );
426
589
        $m->callback( %$ARGS, CallbackName => 'FailedLogin', CallbackPage => '/autohandler' );
427
 
        $m->abort;
428
 
    }
429
 
 
430
 
    $RT::Logger->info("Successful login for @{[$ARGS->{user}]} from $ENV{'REMOTE_ADDR'}");
431
 
    InstantiateNewSession();
432
 
    $HTML::Mason::Commands::session{'CurrentUser'} = $user_obj;
433
 
    $m->callback( %$ARGS, CallbackName => 'SuccessfulLogin', CallbackPage => '/autohandler' );
 
590
        return (0, HTML::Mason::Commands::loc('Your username or password is incorrect'));
 
591
    }
 
592
    else {
 
593
        $RT::Logger->info("Successful login for @{[$ARGS->{user}]} from $ENV{'REMOTE_ADDR'}");
 
594
 
 
595
        # It's important to nab the next page from the session before we blow
 
596
        # the session away
 
597
        my $next = delete $HTML::Mason::Commands::session{'NextPage'}->{$ARGS->{'next'} || ''};
 
598
 
 
599
        InstantiateNewSession();
 
600
        $HTML::Mason::Commands::session{'CurrentUser'} = $user_obj;
 
601
        SendSessionCookie();
 
602
 
 
603
        $m->callback( %$ARGS, CallbackName => 'SuccessfulLogin', CallbackPage => '/autohandler' );
 
604
 
 
605
        # Really the only time we don't want to redirect here is if we were
 
606
        # passed user and pass as query params in the URL.
 
607
        if ($next) {
 
608
            Redirect($next);
 
609
        }
 
610
        elsif ($ARGS->{'next'}) {
 
611
            # Invalid hash, but still wants to go somewhere, take them to /
 
612
            Redirect(RT->Config->Get('WebURL'));
 
613
        }
 
614
 
 
615
        return (1, HTML::Mason::Commands::loc('Logged in'));
 
616
    }
434
617
}
435
618
 
436
619
=head2 LoadSessionFromCookie
497
680
    untie $HTML::Mason::Commands::session;
498
681
    my $uri        = URI->new($redir_to);
499
682
    my $server_uri = URI->new( RT->Config->Get('WebURL') );
 
683
    
 
684
    # Make relative URIs absolute from the server host and scheme
 
685
    $uri->scheme($server_uri->scheme) if not defined $uri->scheme;
 
686
    if (not defined $uri->host) {
 
687
        $uri->host($server_uri->host);
 
688
        $uri->port($server_uri->port);
 
689
    }
500
690
 
501
691
    # If the user is coming in via a non-canonical
502
692
    # hostname, don't redirect them to the canonical host,
639
829
        }
640
830
        $type ||= "application/octet-stream";
641
831
    }
 
832
 
 
833
    # CGI.pm version 3.51 and 3.52 bang charset=iso-8859-1 onto our JS
 
834
    # since we don't specify a charset
 
835
    if ( $type =~ m{application/javascript} &&
 
836
         $type !~ m{charset=([\w-]+)$} ) {
 
837
         $type .= "; charset=utf-8";
 
838
    }
642
839
    $HTML::Mason::Commands::r->content_type($type);
643
 
    open my $fh, "<$file" or die "couldn't open file: $!";
 
840
    open( my $fh, '<', $file ) or die "couldn't open file: $!";
644
841
    binmode($fh);
645
842
    {
646
843
        local $/ = \16384;
684
881
    # Check for plaintext sig
685
882
    return '' if not $html and $content =~ /^(--)?\Q$sig\E$/;
686
883
 
687
 
    # Check for html-formatted sig
688
 
    RT::Interface::Web::EscapeUTF8( \$sig );
 
884
    # Check for html-formatted sig; we don't use EscapeUTF8 here
 
885
    # because we want to precisely match the escaping that FCKEditor
 
886
    # uses. see also 311223f5, which fixed this for 4.0
 
887
    $sig =~ s/&/&amp;/g;
 
888
    $sig =~ s/</&lt;/g;
 
889
    $sig =~ s/>/&gt;/g;
 
890
 
689
891
    return ''
690
892
      if $html
691
893
          and $content =~ m{^(?:<p>)?(--)?\Q$sig\E(?:</p>)?$}s;
2097
2299
    return ( _load_container_object( $obj_type, $obj_id ), $search_id );
2098
2300
}
2099
2301
 
2100
 
eval "require RT::Interface::Web_Vendor";
2101
 
die $@ if ( $@ && $@ !~ qr{^Can't locate RT/Interface/Web_Vendor.pm} );
2102
 
eval "require RT::Interface::Web_Local";
2103
 
die $@ if ( $@ && $@ !~ qr{^Can't locate RT/Interface/Web_Local.pm} );
 
2302
RT::Base->_ImportOverlays();
2104
2303
 
2105
2304
1;