The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
#!/usr/bin/perl  -T
use Test::More;
use Test::Taint;
use Test::Regression;
use Test::Warn;
use English qw(-no_match_vars);
use strict;
use warnings;

if ($OSNAME eq 'MSWin32') {
    my $msg = 'Not running these tests on windows yet';
    plan skip_all => $msg;
    exit(0);
}

plan tests => 11;

use strict;
use warnings;
taint_checking_ok('taint checking is on');

use CGI ();

my $cap_options =
{
        DRIVER => [ 'Generic', { user1 => '123' } ],
        STORE => ['Cookie', SECRET => "Shhh, don't tell anyone", NAME => 'CAPAUTH_DATA', EXPIRY => '+1y'],
        POST_LOGIN_CALLBACK => \&TestAppAuthenticate::post_login,
};

{

    package TestAppAuthenticate;

    use base qw(CGI::Application);
    use CGI::Application::Plugin::Authentication;

    sub setup {
        my $self = shift;
        $self->start_mode('one');
        $self->run_modes([qw(one two three)]);
        $self->authen->protected_runmodes(qw(two three));
        $self->authen->config($cap_options);
    }

    sub one {
        my $self = shift;
        return "<html><body>ONE</body></html>";
    }

    sub two {
        my $self = shift;
        return "<html><body>TWO</body></html>";
    }

    sub three {
        my $self = shift;
        return "<html><body>THREE</body></html>";
    }

    sub post_login {
      my $self = shift;

      my $count=$self->param('post_login')||0;
      $self->param('post_login' => $count + 1 );
    }

}

$ENV{CGI_APP_RETURN_ONLY} = 1;

# successful login
subtest 'straightforward use of destination parameter' => sub {
        plan tests => 5;
        my $query = CGI->new( { authen_username => 'user1', rm => 'two', authen_password=>'123', destination=>'http://news.bbc.co.uk' } );

        my $cgiapp = TestAppAuthenticate->new( QUERY => $query );
        ok_regression(sub {make_output_timeless($cgiapp->run)}, "t/out/redirect", "redirection");

        ok($cgiapp->authen->is_authenticated,'login success');
        is( $cgiapp->authen->username, 'user1', "login success - username set" );
        is( $cgiapp->authen->login_attempts, 0, "successful login - failed login count" );
        is( $cgiapp->param('post_login'),1,'successful login - POST_LOGIN_CALLBACK executed' );
};

subtest 'redirection including CRLF' => sub {
        plan tests => 5;
        my $query = CGI->new( { authen_username => 'user1', rm => 'two', authen_password=>'123', destination=>'http://news.bbc.co.uk\r\nLocation: blah' } );

        my $cgiapp = TestAppAuthenticate->new( QUERY => $query );
        ok_regression(sub {make_output_timeless($cgiapp->run)}, "t/out/crlf", "crlf");

        ok($cgiapp->authen->is_authenticated,'login success');
        is( $cgiapp->authen->username, 'user1', "login success - username set" );
        is( $cgiapp->authen->login_attempts, 0, "successful login - failed login count" );
        is( $cgiapp->param('post_login'),1,'successful login - POST_LOGIN_CALLBACK executed' );
};

subtest 'redirection with constraining taint check' => sub {
        plan tests => 5;
        local $cap_options->{DETAINT_URL_REGEXP} = '^(http\:\/\/www\.perl.org\/[\w\_\%\?\&\;\-\/\@\.\+\$\=\#\:\!\*\"\'\(\)\,]+)$';
        my $query = CGI->new( { authen_username => 'user1', rm => 'two', authen_password=>'123', destination=>'http://news.bbc.co.uk' } );

        my $cgiapp = TestAppAuthenticate->new( QUERY => $query );
        ok_regression(sub {make_output_timeless($cgiapp->run)}, "t/out/restricted", "restricted");

        ok($cgiapp->authen->is_authenticated,'login success');
        is( $cgiapp->authen->username, 'user1', "login success - username set" );
        is( $cgiapp->authen->login_attempts, 0, "successful login - failed login count" );
        is( $cgiapp->param('post_login'),1,'successful login - POST_LOGIN_CALLBACK executed' );
};

subtest 'user name failing taint check' => sub {
        plan tests => 5;
        local $cap_options->{DETAINT_USERNAME_REGEXP} = '^([A-Z]+)$';
        my $query = CGI->new( { authen_username => 'user1', rm => 'two', destination=>'http://news.bbc.co.uk' } );

        my $cgiapp = TestAppAuthenticate->new( QUERY => $query );
        ok_regression(sub {make_output_timeless($cgiapp->run)}, "t/out/username", "username");

        ok(!$cgiapp->authen->is_authenticated,'login failure');
        is( $cgiapp->authen->username, undef, "login failure - username not set" );
        is( $cgiapp->authen->login_attempts, 1, "failed login - failed login count" );
        is( $cgiapp->param('post_login'),1,'failed login - POST_LOGIN_CALLBACK executed' );
};

subtest 'user name failing taint check - basic' => sub {
        plan tests => 5;
        local $cap_options->{LOGIN_FORM}->{DISPLAY_CLASS} = 'Basic';
        local $cap_options->{DETAINT_USERNAME_REGEXP} = '^([A-Z]+)$';
        my $query = CGI->new( { authen_username => 'user1', rm => 'two', destination=>'http://news.bbc.co.uk' } );

        my $cgiapp = TestAppAuthenticate->new( QUERY => $query );
        ok_regression(sub {make_output_timeless($cgiapp->run)}, "t/out/username-basic", "username basic");

        ok(!$cgiapp->authen->is_authenticated,'login failure');
        is( $cgiapp->authen->username, undef, "login failure - username not set" );
        is( $cgiapp->authen->login_attempts, 1, "failed login - failed login count" );
        is( $cgiapp->param('post_login'),1,'failed login - POST_LOGIN_CALLBACK executed' );
};

subtest 'POST_LOGIN_URL usage' => sub {
        plan tests => 5;
        local $cap_options->{POST_LOGIN_URL} = 'http://www.perl.org';
        my $query = CGI->new( { authen_username => 'user1', rm => 'two', authen_password=>'123', destination=>'http://news.bbc.co.uk' } );

        my $cgiapp = TestAppAuthenticate->new( QUERY => $query );
        ok_regression(sub {make_output_timeless($cgiapp->run)}, "t/out/loginurl", "loginurl");

        ok($cgiapp->authen->is_authenticated,'login success');
        is( $cgiapp->authen->username, 'user1', "login success - username set" );
        is( $cgiapp->authen->login_attempts, 0, "successful login - failed login count" );
        is( $cgiapp->param('post_login'),1,'successful login - POST_LOGIN_CALLBACK executed' );
};

subtest 'POST_LOGIN_RUNMODE usage' => sub {
        plan tests => 6;
        local $cap_options->{POST_LOGIN_RUNMODE} = 'three';
        local $cap_options->{POST_LOGIN_URL} = 'http://www.perl.org';
        my $query = CGI->new( { authen_username => 'user1', rm => 'two', authen_password=>'123', destination=>'http://news.bbc.co.uk' } );

        my $cgiapp;
        warning_is {$cgiapp = TestAppAuthenticate->new( QUERY => $query );}
            "authen config warning:  parameter POST_LOGIN_URL ignored since we already have POST_LOGIN_RUNMODE",
            "checking generated warning";
        ok_regression(sub {make_output_timeless($cgiapp->run)}, "t/out/runmode", "runmode");

        ok($cgiapp->authen->is_authenticated,'login success');
        is( $cgiapp->authen->username, 'user1', "login success - username set" );
        is( $cgiapp->authen->login_attempts, 0, "successful login - failed login count" );
        is( $cgiapp->param('post_login'),1,'successful login - POST_LOGIN_CALLBACK executed' );
        
};

subtest 'LOGOUT usage' => sub {
        plan tests => 2;
        local $cap_options->{POST_LOGIN_RUNMODE} = 'three';
        my $query = CGI->new( { authen_username => 'user1', rm => 'two', authen_password=>'123', authen_logout=>1, destination=>'http://news.bbc.co.uk' } );

        my $cgiapp = TestAppAuthenticate->new( QUERY => $query );
        ok_regression(sub {make_output_timeless($cgiapp->run)}, "t/out/logout", "logout");
        ok(!$cgiapp->authen->is_authenticated,'logout success');
        
};

subtest 'Redirection failure' => sub {
        plan tests => 1;
        local $ENV{PATH_INFO} = '!!!!';
        local $cap_options->{DETAINT_URL_REGEXP} = '^(\w+)$';
        my $query = CGI->new( { rm => 'two'} );

        my $cgiapp = TestAppAuthenticate->new( QUERY => $query );
        ok_regression(sub {make_output_timeless($cgiapp->run)}, "t/out/redirection_failure", "redirection_failure");

};

subtest 'Redirection failure [Basic]' => sub {
        plan tests => 1;
        local $ENV{PATH_INFO} = '!!!!';
        local $cap_options->{DETAINT_URL_REGEXP} = '^(\w+)$';
        local $cap_options->{LOGIN_FORM}->{DISPLAY_CLASS} = 'Basic';
        my $query = CGI->new( { rm => 'two'} );

        my $cgiapp = TestAppAuthenticate->new( QUERY => $query );
        ok_regression(sub {make_output_timeless($cgiapp->run)}, "t/out/redirection_failure_basic", "redirection_failure [Basic]");

};

sub make_output_timeless {
        my $output = shift;
        $output =~ s/^(Set-Cookie: CAPAUTH_DATA=\w+\%3D(?:\%3D)?\; path=\/\; expires=\w{3},\s\d{2}\-\w{3}\-\d{4}\s\d{2}:\d{2}:\d{2}\s\w{3})([\r\n\s]*)$/Set-Cookie: CAPAUTH_DATA=; path=\/; expires=;$2/m;
        $output =~ s/^(Expires:\s\w{3},\s\d{2}\s\w{3}\s\d{4}\s\d{2}:\d{2}:\d{2}\s\w{3})([\r\n\s]*)$/Expires$2/m;
        $output =~ s/^(Date:\s\w{3},\s\d{2}\s\w{3}\s\d{4}\s\d{2}:\d{2}:\d{2}\s\w{3})([\r\n\s]*)$/Date$2/m;
        #$output =~ s/\r//g;
        return $output;
}