lemonldap-ng/lemonldap-ng-portal/t/test-lib.pm

437 lines
12 KiB
Perl
Raw Normal View History

2016-04-03 18:27:13 +02:00
# Base library for portal tests
2016-11-14 13:34:46 +01:00
package main;
2016-04-03 18:27:13 +02:00
use strict;
use Data::Dumper;
2016-11-29 22:44:33 +01:00
use LWP::UserAgent;
2016-12-27 21:00:20 +01:00
use URI::Escape;
2018-06-26 21:45:55 +02:00
use Lemonldap::NG::Common::FormEncode;
2016-04-03 18:27:13 +02:00
use 5.10.0;
2016-04-05 07:23:42 +02:00
2016-12-20 12:53:31 +01:00
no warnings 'redefine';
2016-11-29 22:44:33 +01:00
BEGIN {
use_ok('Lemonldap::NG::Portal::Main');
}
2016-04-05 07:23:42 +02:00
our $count = 1;
2018-05-07 17:17:55 +02:00
$Data::Dumper::Deparse = 1;
$Data::Dumper::Sortkeys = 1;
$Data::Dumper::Useperl = 1;
2016-04-05 07:23:42 +02:00
my $ini;
2016-04-03 18:27:13 +02:00
sub count {
my $c = shift;
$count += $c if ($c);
return $count;
}
2016-11-29 22:44:33 +01:00
sub main::explain {
2016-04-03 18:27:13 +02:00
my ( $get, $ref ) = @_;
$get = Dumper($get) if ( ref $get );
2017-03-22 19:11:45 +01:00
diag("Expect $ref, get $get\n");
2016-04-03 18:27:13 +02:00
}
2016-11-14 13:34:46 +01:00
sub clean_sessions {
opendir D, 't/sessions' or die $!;
foreach ( grep { /^[^\.]/ } readdir(D) ) {
unlink "t/sessions/$_", "t/sessions/lock/Apache-Session-$_.lock";
}
2016-11-22 21:55:10 +01:00
foreach my $dir (qw(t/sessions/lock t/sessions/saml/lock t/sessions/saml)) {
if ( -d $dir ) {
opendir D, $dir or die $!;
foreach ( grep { /^[^\.]/ } readdir(D) ) {
unlink "$dir/$_";
}
}
2016-11-14 13:34:46 +01:00
}
my $cache = getCache();
$cache->clear;
}
sub getCache {
2017-03-13 10:16:34 +01:00
require Cache::FileCache;
return Cache::FileCache->new(
{
namespace => 'lemonldap-ng-session',
cache_root => 't/',
cache_depth => 0,
}
);
2016-11-14 13:34:46 +01:00
}
2016-12-23 07:41:03 +01:00
sub expectRedirection {
my ( $res, $location ) = @_;
2017-01-02 21:35:37 +01:00
ok( $res->[0] == 302, ' Get redirection' )
2016-12-23 07:41:03 +01:00
or explain( $res->[0], 302 );
2017-01-01 10:43:48 +01:00
count(1);
2016-12-23 07:41:03 +01:00
if ( ref $location ) {
my @match;
@match = ( getRedirection($res) =~ $location );
2017-01-02 21:35:37 +01:00
ok( @match, ' Location header found' )
2016-12-23 07:41:03 +01:00
or explain( $res->[1], "Location match: " . Dumper($location) );
2017-01-01 10:43:48 +01:00
count(1);
2016-12-23 07:41:03 +01:00
return @match;
}
else {
2017-01-02 21:35:37 +01:00
ok( getRedirection($res) eq $location, " Location is $location" )
2016-12-23 07:41:03 +01:00
or explain( $res->[1], "Location => $location" );
2017-01-10 22:43:34 +01:00
count(1);
2016-12-23 07:41:03 +01:00
}
}
2016-12-27 21:00:20 +01:00
sub expectAutoPost {
2017-01-10 22:43:34 +01:00
my @r = expectForm(@_);
2017-01-01 10:43:48 +01:00
my $method = pop @r;
2018-07-19 08:38:51 +02:00
ok( $method =~ /^post$/i, ' Method is POST' ) or explain( $method, 'POST' );
2017-01-01 10:43:48 +01:00
count(1);
2017-01-01 13:32:38 +01:00
return @r;
2017-01-01 10:43:48 +01:00
}
sub expectForm {
2016-12-27 21:00:20 +01:00
my ( $res, $hostRe, $uriRe, @requiredFields ) = @_;
expectOK($res);
2017-01-01 10:43:48 +01:00
count(1);
2016-12-27 21:00:20 +01:00
if (
ok(
$res->[2]->[0] =~
2017-02-02 13:29:59 +01:00
m@<form.+?action="(?:(?:http://([^/]+))?(/.*?)?|(#))".+method="(post|get)"@is,
2017-01-02 21:35:37 +01:00
' Page contains a form'
2016-12-27 21:00:20 +01:00
)
)
{
2017-01-01 10:43:48 +01:00
my ( $host, $uri, $hash, $method ) = ( $1, $2, $3, $4 );
2017-01-10 22:43:34 +01:00
if ( $hash and $hash eq '#' ) {
2017-01-01 10:43:48 +01:00
$host = '#';
2017-01-10 22:43:34 +01:00
$uri = '';
2017-01-01 10:43:48 +01:00
}
2016-12-27 21:00:20 +01:00
if ($hostRe) {
if ( ref $hostRe ) {
2017-01-02 21:35:37 +01:00
ok( $host =~ $hostRe, ' Host match' )
2016-12-27 21:00:20 +01:00
or explain( $host, $hostRe );
}
else {
2017-01-02 21:35:37 +01:00
ok( $host eq $hostRe, ' Host match' )
2016-12-27 21:00:20 +01:00
or explain( $host, $hostRe );
}
count(1);
}
if ($uriRe) {
if ( ref $uriRe ) {
2017-01-02 21:35:37 +01:00
ok( $uri =~ $uriRe, ' URI match' ) or explain( $uri, $uriRe );
2016-12-27 21:00:20 +01:00
}
else {
2017-01-02 21:35:37 +01:00
ok( $uri eq $uriRe, ' URI match' ) or explain( $uri, $uriRe );
2016-12-27 21:00:20 +01:00
}
count(1);
}
2017-01-10 22:43:34 +01:00
my %fields =
2017-01-25 13:02:27 +01:00
( $res->[2]->[0] =~
m#<input.+?name="([^"]+)"[^>]+?value="([^"]*?)"#gs );
2018-06-26 23:27:00 +02:00
my $query = join( '&',
map { "$_=" . uri_escape( uri_unescape( $fields{$_} ) ) }
keys(%fields) );
2016-12-27 21:00:20 +01:00
foreach my $f (@requiredFields) {
2017-01-02 21:35:37 +01:00
ok( defined $fields{$f}, qq{ Field "$f" is defined} );
2016-12-27 21:00:20 +01:00
count(1);
}
2018-03-21 14:37:44 +01:00
exceptCspFormOK( $res, $host );
2017-01-01 13:32:38 +01:00
return ( $host, $uri, $query, $method );
2016-12-27 21:00:20 +01:00
}
else {
return ();
}
}
2016-12-23 07:41:03 +01:00
sub expectAuthenticatedAs {
my ( $res, $user ) = @_;
2017-01-10 22:43:34 +01:00
ok( getHeader( $res, 'Lm-Remote-User' ) eq $user,
" Authenticated as $user" )
2016-12-23 07:41:03 +01:00
or explain( $res->[1], "Lm-Remote-User => $user" );
count(1);
}
sub expectOK {
my ($res) = @_;
2017-01-02 21:35:37 +01:00
ok( $res->[0] == 200, ' HTTP code is 200' ) or explain( $res, 200 );
2016-12-23 07:41:03 +01:00
count(1);
}
sub expectBadRequest {
my ($res) = @_;
2017-01-02 21:35:37 +01:00
ok( $res->[0] == 400, ' HTTP code is 400' ) or explain( $res->[0], 400 );
2016-12-23 07:41:03 +01:00
count(1);
}
sub expectReject {
2017-03-22 19:11:45 +01:00
my ( $res, $code ) = @_;
2017-01-02 21:35:37 +01:00
ok( $res->[0] == 401, ' Response is 401' ) or explain( $res->[0], 401 );
2017-03-22 19:11:45 +01:00
eval { $res = JSON::from_json( $res->[2]->[0] ) };
ok( not($@), 'Content is JSON' )
or explain( $res->[2]->[0], 'JSON content' );
if ( defined $code ) {
ok( $res->{error} == $code, "Error code is $code" )
or explain( $res->{error}, $code );
}
else {
pass("Error code is $res->{error}");
2017-03-22 19:11:45 +01:00
}
count(3);
2016-12-23 07:41:03 +01:00
}
sub expectCookie {
my ( $res, $cookieName ) = @_;
$cookieName ||= 'lemonldap';
my $cookies = getCookies($res);
my $id;
2017-01-22 23:51:22 +01:00
ok(
defined( $id = $cookies->{$cookieName} ),
" Get cookie $cookieName ($id)"
) or explain( $res->[1], "Set-Cookie: $cookieName=something" );
2016-12-23 07:41:03 +01:00
count(1);
return $id;
}
2018-03-21 14:37:44 +01:00
sub exceptCspFormOK {
my ( $res, $host ) = @_;
return 1 unless ($host);
my $csp = getHeader( $res, 'Content-Security-Policy' );
return 1 unless ($csp);
unless ( $csp =~ s/^.*form-action (.*?)(?:;.*)?$/$1/ ) {
$csp =~ s/^.*default-src (.*?)(?:;.*)?$/$1/;
}
if ( $csp =~ /\s\*(?:\s.*)?\s*$/
or ( $host eq '#' and $csp =~ /'self'/ )
or $csp =~ m#\bhttps?://$host\b# )
2018-03-21 14:37:44 +01:00
{
pass(" CSP header authorize POST request to $host");
2018-03-21 14:37:44 +01:00
}
else {
fail(" CSP header authorize POST request to $host");
2018-03-21 14:37:44 +01:00
explain( $res->[1], "form-action ... $host" );
}
count(1);
}
2016-12-23 07:41:03 +01:00
sub getCookies {
my ($resp) = @_;
my @hdrs = @{ $resp->[1] };
my $res = {};
while ( my $name = shift @hdrs ) {
my $v = shift @hdrs;
if ( $name eq 'Set-Cookie' ) {
if ( $v =~ /^(\w+)=([^;]*)/ ) {
$res->{$1} = $2;
}
}
}
return $res;
}
sub getHeader {
my ( $resp, $hname ) = @_;
my @hdrs = @{ $resp->[1] };
my $res = {};
while ( my $name = shift @hdrs ) {
my $v = shift @hdrs;
if ( $name eq $hname ) {
return $v;
}
}
return undef;
}
sub getRedirection {
my ($resp) = @_;
return getHeader( $resp, 'Location' );
}
sub getUser {
my ($resp) = @_;
return getHeader( $resp, 'Lm-Remote-User' );
}
2016-11-14 13:34:46 +01:00
package LLNG::Manager::Test;
use strict;
use Mouse;
extends 'Lemonldap::NG::Common::PSGI::Cli::Lib';
our $defaultIni = {
configStorage => {
type => 'File',
dirName => 't',
},
localSessionStorage => 'Cache::FileCache',
localSessionStorageOptions => {
namespace => 'lemonldap-ng-session',
cache_root => 't/',
cache_depth => 0,
},
logLevel => 'error',
cookieName => 'lemonldap',
domain => 'example.com',
templateDir => 'site/templates',
2018-05-16 13:22:15 +02:00
staticPrefix => '/static',
securedCookie => 0,
https => 0,
2016-11-14 13:34:46 +01:00
};
has app => (
is => 'rw',
isa => 'CodeRef',
2016-11-14 13:34:46 +01:00
);
2018-08-02 22:00:41 +02:00
has class => ( is => 'ro', default => 'Lemonldap::NG::Portal::Main' );
2017-01-03 21:18:05 +01:00
has p => ( is => 'rw' );
has ini => (
is => 'rw',
default => sub { $defaultIni; },
trigger => sub {
my ( $self, $ini ) = @_;
foreach my $k ( keys %$defaultIni ) {
$ini->{$k} //= $defaultIni->{$k};
}
$self->{ini} = $ini;
2018-09-02 17:31:58 +02:00
main::ok( $self->{p} = $self->class->new(), 'Portal object' );
2017-01-10 22:43:34 +01:00
main::ok( $self->{p}->init($ini), 'Init' );
main::ok( $self->{app} = $self->{p}->run(), 'Portal app' );
2017-01-03 21:18:05 +01:00
main::count(3);
no warnings 'redefine';
eval
'sub Lemonldap::NG::Common::Logger::Std::error {return $_[0]->warn($_[1])}';
2017-03-01 18:35:12 +01:00
$Lemonldap::NG::Portal::UserDB::Demo::demoAccounts{french} = {
2017-03-03 18:25:03 +01:00
uid => 'french',
cn => 'Frédéric Accents',
2017-03-01 18:35:12 +01:00
mail => 'fa@badwolf.org',
};
$Lemonldap::NG::Portal::UserDB::Demo::demoAccounts{russian} = {
2017-03-03 18:25:03 +01:00
uid => 'russian',
cn => 'Русский',
2017-03-01 18:35:12 +01:00
mail => 'ru@badwolf.org',
};
$self;
2016-11-14 13:34:46 +01:00
}
);
2016-05-22 19:06:55 +02:00
sub logout {
my ( $self, $id ) = @_;
2016-05-22 19:06:55 +02:00
my $res;
2016-11-14 13:34:46 +01:00
main::ok(
$res = $self->_get(
2016-05-22 19:06:55 +02:00
'/',
query => 'logout',
cookie => "lemonldap=$id",
accept => 'text/html'
),
2016-12-23 07:41:03 +01:00
'Logout request'
2016-05-22 19:06:55 +02:00
);
2017-01-15 09:10:16 +01:00
main::ok( $res->[0] == 200, ' Response is 200' )
or main::explain( $res->[0], 200 );
my $c;
main::ok(
2016-12-23 07:41:03 +01:00
( defined( $c = main::getCookies($res)->{lemonldap} ) and not $c ),
2017-01-15 09:10:16 +01:00
' Cookie is deleted' )
or main::explain( $res->[1], "Set-Cookie => 'lemonldap='" );
2018-07-06 12:15:14 +02:00
main::ok( not( main::getCookies($res)->{lemonldappdata} ), ' No pdata' );
2016-11-14 13:34:46 +01:00
main::ok( $res = $self->_get( '/', cookie => "lemonldap=$id" ),
'Disconnect request' )
or explain( $res, '[<code>,<hdrs>,<content>]' );
2017-01-15 09:10:16 +01:00
main::ok( $res->[0] == 401, ' Response is 401' )
2017-01-10 22:43:34 +01:00
or main::explain( $res, 401 );
2018-07-06 12:15:14 +02:00
main::count(6);
2016-05-22 19:06:55 +02:00
}
2016-04-05 07:23:42 +02:00
sub _get {
my ( $self, $path, %args ) = @_;
my $res = $self->app->(
2016-04-05 07:23:42 +02:00
{
'HTTP_ACCEPT' => $args{accept}
|| 'application/json, text/plain, */*',
'HTTP_ACCEPT_LANGUAGE' => 'fr,fr-FR;q=0.8,en-US;q=0.5,en;q=0.3',
2016-05-22 13:27:37 +02:00
'HTTP_CACHE_CONTROL' => 'max-age=0',
2016-05-23 18:55:23 +02:00
( $args{cookie} ? ( HTTP_COOKIE => $args{cookie} ) : () ),
2016-05-22 13:27:37 +02:00
'HTTP_HOST' => 'auth.example.com',
'HTTP_USER_AGENT' =>
'Mozilla/5.0 (VAX-4000; rv:36.0) Gecko/20350101 Firefox',
2016-05-23 18:55:23 +02:00
'PATH_INFO' => $path,
( $args{referer} ? ( REFERER => $args{referer} ) : () ),
2016-05-22 13:27:37 +02:00
(
2017-10-27 15:11:30 +02:00
$args{ip} ? ( 'REMOTE_ADDR' => $args{ip} )
: ( 'REMOTE_ADDR' => '127.0.0.1' )
),
(
$args{remote_user} ? ( 'REMOTE_USER' => $args{remote_user} )
2016-05-22 13:27:37 +02:00
: ()
),
2017-01-22 23:51:22 +01:00
'REQUEST_METHOD' => $args{method} || 'GET',
2018-09-02 17:31:58 +02:00
'REQUEST_URI' => $path . ( $args{query} ? "?$args{query}" : '' ),
2016-05-23 18:55:23 +02:00
( $args{query} ? ( QUERY_STRING => $args{query} ) : () ),
2016-05-22 13:27:37 +02:00
'SCRIPT_NAME' => '',
'SERVER_NAME' => 'auth.example.com',
2016-12-07 23:30:00 +01:00
'SERVER_PORT' => '80',
2016-04-05 07:23:42 +02:00
'SERVER_PROTOCOL' => 'HTTP/1.1',
2016-05-25 21:30:43 +02:00
( $args{custom} ? %{ $args{custom} } : () ),
2016-04-05 07:23:42 +02:00
}
);
return $res;
2016-04-05 07:23:42 +02:00
}
2016-05-30 22:20:50 +02:00
sub _post {
my ( $self, $path, $body, %args ) = @_;
die "$body must be a IO::Handle"
unless ( ref($body) and $body->can('read') );
my $res = $self->app->(
2016-05-30 22:20:50 +02:00
{
'HTTP_ACCEPT' => $args{accept}
|| 'application/json, text/plain, */*',
'HTTP_ACCEPT_LANGUAGE' => 'fr,fr-FR;q=0.8,en-US;q=0.5,en;q=0.3',
'HTTP_CACHE_CONTROL' => 'max-age=0',
( $args{cookie} ? ( HTTP_COOKIE => $args{cookie} ) : () ),
'HTTP_HOST' => 'auth.example.com',
'HTTP_USER_AGENT' =>
'Mozilla/5.0 (VAX-4000; rv:36.0) Gecko/20350101 Firefox',
'PATH_INFO' => $path,
( $args{query} ? ( QUERY_STRING => $args{query} ) : () ),
( $args{referer} ? ( REFERER => $args{referer} ) : () ),
2016-05-30 22:20:50 +02:00
'REMOTE_ADDR' => '127.0.0.1',
(
$args{remote_user}
? ( 'REMOTE_USER' => $args{remote_user} )
: ()
),
2017-01-22 23:51:22 +01:00
'REQUEST_METHOD' => $args{method} || 'POST',
2018-09-02 17:31:58 +02:00
'REQUEST_URI' => $path . ( $args{query} ? "?$args{query}" : '' ),
'SCRIPT_NAME' => '',
'SERVER_NAME' => 'auth.example.com',
'SERVER_PORT' => '80',
2016-05-30 22:20:50 +02:00
'SERVER_PROTOCOL' => 'HTTP/1.1',
( $args{custom} ? %{ $args{custom} } : () ),
2017-01-07 21:37:07 +01:00
'psgix.input.buffered' => 0,
2016-05-30 22:20:50 +02:00
'psgi.input' => $body,
'CONTENT_LENGTH' => $args{length} // scalar( ( stat $body )[7] ),
'CONTENT_TYPE' => $args{type}
|| 'application/x-www-form-urlencoded',
}
);
return $res;
2016-05-30 22:20:50 +02:00
}
2017-01-22 23:51:22 +01:00
sub _delete {
my ( $self, $path, %args ) = @_;
$args{method} = 'DELETE';
return $self->_get( $path, %args );
}
sub _put {
my ( $self, $path, $body, %args ) = @_;
$args{method} = 'PUT';
return $self->_post( $path, $body, %args );
}
2016-04-03 18:27:13 +02:00
1;