e1f927a195
service= redirect URL is not checked when logging out from CAS, to avoid insecure redirect attacks. The verification is only made if CAS access control is enabled. In order for this to work in common cases (applications redirects to an unprotected page after logout), we add CAS App domains to the list of globally trusted domains. If your application wants to redirect to a third-party domain, it needs to be added to LLNG's trustedDomains
250 lines
6.7 KiB
Perl
250 lines
6.7 KiB
Perl
use lib 'inc';
|
|
use Test::More; # skip_all => 'CAS is in rebuild';
|
|
use strict;
|
|
use IO::String;
|
|
use LWP::UserAgent;
|
|
use LWP::Protocol::PSGI;
|
|
use MIME::Base64;
|
|
|
|
BEGIN {
|
|
require 't/test-lib.pm';
|
|
}
|
|
|
|
my $debug = 'error';
|
|
my ( $issuer, $sp, $res );
|
|
my %handlerOR = ( issuer => [], sp => [] );
|
|
|
|
# Redefine LWP methods for tests
|
|
LWP::Protocol::PSGI->register(
|
|
sub {
|
|
my $req = Plack::Request->new(@_);
|
|
ok( $req->uri =~ m#http://auth.((?:id|s)p).com([^\?]*)(?:\?(.*))?$#,
|
|
'SOAP request' );
|
|
my $host = $1;
|
|
my $url = $2;
|
|
my $query = $3;
|
|
my $res;
|
|
my $client = ( $host eq 'idp' ? $issuer : $sp );
|
|
if ( $req->method eq 'POST' ) {
|
|
my $s = $req->content;
|
|
ok(
|
|
$res = $client->_post(
|
|
$url, IO::String->new($s),
|
|
length => length($s),
|
|
query => $query,
|
|
type => 'application/xml',
|
|
),
|
|
"Execute POST request to $url"
|
|
);
|
|
}
|
|
else {
|
|
ok(
|
|
$res = $client->_get(
|
|
$url,
|
|
type => 'application/xml',
|
|
query => $query,
|
|
),
|
|
"Execute request to $url"
|
|
);
|
|
}
|
|
expectOK($res);
|
|
ok( getHeader( $res, 'Content-Type' ) =~ m#xml#, 'Content is XML' )
|
|
or explain( $res->[1], 'Content-Type => application/xml' );
|
|
count(3);
|
|
return $res;
|
|
}
|
|
);
|
|
|
|
ok( $issuer = issuer(), 'Issuer portal' );
|
|
$handlerOR{issuer} = \@Lemonldap::NG::Handler::Main::_onReload;
|
|
count(1);
|
|
switch ('sp');
|
|
&Lemonldap::NG::Handler::Main::cfgNum( 0, 0 );
|
|
|
|
ok( $sp = sp(), 'SP portal' );
|
|
count(1);
|
|
$handlerOR{sp} = \@Lemonldap::NG::Handler::Main::_onReload;
|
|
|
|
# Simple SP access
|
|
ok(
|
|
$res = $sp->_get(
|
|
'/', accept => 'text/html',
|
|
),
|
|
'Unauth SP request'
|
|
);
|
|
count(1);
|
|
ok( expectCookie( $res, 'llngcasserver' ) eq 'idp', 'Get CAS server cookie' );
|
|
count(1);
|
|
expectRedirection( $res,
|
|
'http://auth.idp.com/cas/login?service=http%3A%2F%2Fauth.sp.com%2F' );
|
|
|
|
# Query IdP
|
|
switch ('issuer');
|
|
ok(
|
|
$res = $issuer->_get(
|
|
'/cas/login',
|
|
query => 'service=http://auth.sp.com/',
|
|
accept => 'text/html'
|
|
),
|
|
'Query CAS server'
|
|
);
|
|
count(1);
|
|
expectOK($res);
|
|
my $pdata = 'lemonldappdata=' . expectCookie( $res, 'lemonldappdata' );
|
|
|
|
# Try to authenticate to IdP
|
|
my $body = $res->[2]->[0];
|
|
$body =~ s/^.*?<form.*?>//s;
|
|
$body =~ s#</form>.*$##s;
|
|
my %fields =
|
|
( $body =~ /<input type="hidden".+?name="(.+?)".+?value="(.*?)"/sg );
|
|
$fields{user} = $fields{password} = 'french';
|
|
use URI::Escape;
|
|
my $s = join( '&', map { "$_=" . uri_escape( $fields{$_} ) } keys %fields );
|
|
ok(
|
|
$res = $issuer->_post(
|
|
'/cas/login',
|
|
IO::String->new($s),
|
|
cookie => $pdata,
|
|
accept => 'text/html',
|
|
length => length($s),
|
|
),
|
|
'Post authentication'
|
|
);
|
|
count(1);
|
|
my $idpId = expectCookie($res);
|
|
|
|
# Expect pdata to be cleared
|
|
$pdata = expectCookie( $res, 'lemonldappdata' );
|
|
ok( $pdata !~ 'issuerRequestsaml', 'SAML request cleared from pdata' );
|
|
count(1);
|
|
|
|
my ($query) =
|
|
expectRedirection( $res, qr#^http://auth.sp.com/\?(ticket=[^&]+)$# );
|
|
|
|
# Back to SP
|
|
switch ('sp');
|
|
ok(
|
|
$res = $sp->_get(
|
|
'/',
|
|
query => $query,
|
|
accept => 'text/html',
|
|
cookie => 'llngcasserver=idp',
|
|
),
|
|
'Query SP with ticket'
|
|
);
|
|
count(1);
|
|
my $spId = expectCookie($res);
|
|
|
|
# Test authentication
|
|
ok( $res = $sp->_get( '/', cookie => "lemonldap=$spId,llngcasserver=idp" ),
|
|
'Get / on SP' );
|
|
count(1);
|
|
expectOK($res);
|
|
expectAuthenticatedAs( $res, 'french' );
|
|
|
|
# Test attributes
|
|
ok( $res = $sp->_get("/sessions/global/$spId"), 'Get UTF-8' );
|
|
expectOK($res);
|
|
ok( $res = eval { JSON::from_json( $res->[2]->[0] ) }, ' GET JSON' )
|
|
or print STDERR $@;
|
|
ok( $res->{cn} eq 'Frédéric Accents', 'UTF-8 values' )
|
|
or explain( $res, 'cn => Frédéric Accents' );
|
|
count(3);
|
|
|
|
# Logout initiated by CAS, try with invalid service URL first
|
|
switch ('issuer');
|
|
ok(
|
|
$res = $issuer->_get(
|
|
'/cas/logout',
|
|
query => 'service=http://url.test/',
|
|
cookie => "lemonldap=$idpId,llngcasserver=idp",
|
|
accept => 'text/html'
|
|
),
|
|
'Query SP for logout'
|
|
);
|
|
count(1);
|
|
ok( $res->[2]->[0] =~ m%<span trmsg="37"></span>%, ' PE37 found' );
|
|
count(1);
|
|
|
|
# Logout initiated by CAS, try with valid service URL
|
|
ok(
|
|
$res = $issuer->_get(
|
|
'/cas/logout',
|
|
query => 'service=http://auth.sp.com/',
|
|
cookie => "lemonldap=$idpId,llngcasserver=idp",
|
|
accept => 'text/html'
|
|
),
|
|
'Query SP for logout'
|
|
);
|
|
count(1);
|
|
|
|
expectRedirection( $res, 'http://auth.sp.com/' );
|
|
|
|
# Verify that user has been disconnected
|
|
ok( $res = $issuer->_get( '/', cookie => "lemonldap=$idpId" ), 'Query IdP' );
|
|
count(1);
|
|
expectReject($res);
|
|
|
|
clean_sessions();
|
|
done_testing( count() );
|
|
|
|
sub switch {
|
|
my $type = shift;
|
|
@Lemonldap::NG::Handler::Main::_onReload = @{
|
|
$handlerOR{$type};
|
|
};
|
|
}
|
|
|
|
sub issuer {
|
|
return LLNG::Manager::Test->new( {
|
|
ini => {
|
|
logLevel => $debug,
|
|
domain => 'idp.com',
|
|
portal => 'http://auth.idp.com',
|
|
authentication => 'Demo',
|
|
userDB => 'Same',
|
|
issuerDBCASActivation => 1,
|
|
casAttr => 'uid',
|
|
casAttributes => { cn => 'cn', uid => 'uid', },
|
|
casAccessControlPolicy => 'error',
|
|
multiValuesSeparator => ';',
|
|
casAppMetaDataOptions => {
|
|
sp => {
|
|
casAppMetaDataOptionsService => 'http://auth.sp.com',
|
|
},
|
|
},
|
|
}
|
|
}
|
|
);
|
|
}
|
|
|
|
sub sp {
|
|
return LLNG::Manager::Test->new( {
|
|
ini => {
|
|
logLevel => $debug,
|
|
domain => 'sp.com',
|
|
portal => 'http://auth.sp.com',
|
|
authentication => 'CAS',
|
|
userDB => 'CAS',
|
|
restSessionServer => 1,
|
|
issuerDBCASActivation => 0,
|
|
multiValuesSeparator => ';',
|
|
casSrvMetaDataExportedVars => {
|
|
idp => {
|
|
cn => 'cn',
|
|
mail => 'mail',
|
|
uid => 'uid',
|
|
}
|
|
},
|
|
casSrvMetaDataOptions => {
|
|
idp => {
|
|
casSrvMetaDataOptionsUrl => 'http://auth.idp.com/cas',
|
|
casSrvMetaDataOptionsGateway => 0,
|
|
}
|
|
},
|
|
},
|
|
}
|
|
);
|
|
}
|