SAML in progress (#595)

To do: authSAML SOAP server
This commit is contained in:
Xavier Guimard 2016-12-17 07:58:53 +00:00
parent 8e2418ceb8
commit ec83414576
6 changed files with 187 additions and 28 deletions

View File

@ -19,10 +19,11 @@ our $VERSION = '2.0.0';
extends 'Lemonldap::NG::Portal::Main::Issuer', extends 'Lemonldap::NG::Portal::Main::Issuer',
'Lemonldap::NG::Portal::Lib::SAML'; 'Lemonldap::NG::Portal::Lib::SAML';
has ssoUrlRe => ( is => 'rw' ); has ssoUrlRe => ( is => 'rw' );
has sloRe => ( is => 'rw' ); has sloRe => ( is => 'rw' );
has artRe => ( is => 'rw' ); has artRe => ( is => 'rw' );
has soapSloRe => ( is => 'rw' ); has soapSloRe => ( is => 'rw' );
has sloRelaySoapRe => ( is => 'rw' );
# INITIALIZATION # INITIALIZATION
@ -76,6 +77,8 @@ qr/^($saml_sso_soap_url|$saml_sso_soap_url_ret|$saml_sso_get_url|$saml_sso_get_u
qr/^($saml_slo_soap_url|$saml_slo_soap_url_ret|$saml_slo_get_url|$saml_slo_get_url_ret|$saml_slo_post_url|$saml_slo_post_url_ret)(?:\?.*)?$/i qr/^($saml_slo_soap_url|$saml_slo_soap_url_ret|$saml_slo_get_url|$saml_slo_get_url_ret|$saml_slo_post_url|$saml_slo_post_url_ret)(?:\?.*)?$/i
); );
$self->sloRelaySoapRe(qr#^/saml/relaySingleLogoutSOAP(?:\?.*)?$#i);
return ( return (
$self->Lemonldap::NG::Portal::Main::Issuer::init() $self->Lemonldap::NG::Portal::Main::Issuer::init()
@ -104,7 +107,20 @@ sub _pRedirect {
return $self->soapSloServer($req); return $self->soapSloServer($req);
} }
else { else {
return $self->SUPER::_pRedirect($req); $req->parseBody;
return $self->SUPER::_redirect($req);
}
}
# Override _redirect to catch SLO relay
sub _redirect {
my ( $self, $req ) = @_;
if ( $req->uri =~ $self->sloRelaySoapRe ) {
return $self->sloRelaySoap($req);
}
else {
return $self->SUPER::_redirect($req);
} }
} }
@ -1461,6 +1477,105 @@ sub logout {
return PE_OK; return PE_OK;
} }
sub sloRelaySoap {
my ( $self, $req ) = @_;
$self->lmLog( "URL " . $req->uri . " detected as a SOAP relay service URL",
'debug' );
# Check if relay parameter is present (mandatory)
my $relayID;
unless ( $relayID = $req->param('relay') ) {
$self->lmLog( "No relayID detected", 'error' );
return $self->imgnok($req);
}
# Retrieve the corresponding data from samlStorage
my $relayInfos = $self->getSamlSession($relayID);
unless ($relayInfos) {
$self->lmLog( "Could not get relay session $relayID", 'error' );
return $self->imgnok($req);
}
$self->lmLog( "Found relay session $relayID", 'debug' );
# Rebuild the logout object
my $logout;
unless ( $logout = $self->createLogout( $self->lassoServer ) ) {
$self->lmLog( "Could not rebuild logout object", 'error' );
return $self->imgnok($req);
}
# Load Session and Identity if they exist
my $session = $relayInfos->data->{_lassoSessionDump};
my $identity = $relayInfos->data->{_lassoIdentityDump};
my $providerID = $relayInfos->data->{_providerID};
my $relayState = $relayInfos->data->{_relayState};
my $spConfKey = $self->spList->{$providerID}->{confKey};
if ($session) {
unless ( $self->setSessionFromDump( $logout, $session ) ) {
$self->lmLog( "Unable to load Lasso Session", 'error' );
return $self->imgnok($req);
}
$self->lmLog( "Lasso Session loaded", 'debug' );
}
if ($identity) {
unless ( $self->setIdentityFromDump( $logout, $identity ) ) {
$self->lmLog( "Unable to load Lasso Identity", 'error' );
return $self->imgnok($req);
}
$self->lmLog( "Lasso Identity loaded", 'debug' );
}
# Send the logout request
my ( $rstatus, $rmethod, $rinfo ) =
$self->sendLogoutRequestToProvider( $req, $logout, $providerID,
Lasso::Constants::HTTP_METHOD_SOAP );
unless ($rstatus) {
$self->lmLog( "Fail to process SOAP logout request to $providerID",
'error' );
return $self->imgnok($req);
}
# Store success status for this SLO request
my $sloStatusSessionInfos = $self->getSamlSession($relayState);
if ($sloStatusSessionInfos) {
$sloStatusSessionInfos->update( { $spConfKey => 1 } );
$self->lmLog( "Store SLO status for $spConfKey in session $relayState",
'debug' );
}
else {
$self->lmLog(
"Unable to store SLO status for $spConfKey in session $relayState",
'warn'
);
}
# Delete relay session
$relayInfos->remove();
# SLO response is OK
$self->lmLog( "Display OK status for SLO on $spConfKey", 'debug' );
return $self->imgok($req);
}
# INTERNAL METHODS # INTERNAL METHODS
sub imgok {
my ( $self, $req, ) = @_;
return $self->sendImage( $req, 'ok.png' );
}
sub imgnok {
my ( $self, $req, ) = @_;
return $self->sendImage( $req, 'warning.png' );
}
sub sendImage {
my ( $self, $req,, $img ) = @_;
return $self->p->staticFile( $req, "common/$img", 'image/png' );
}
1; 1;

View File

@ -61,15 +61,8 @@ sub display {
# 1. Good authentication # 1. Good authentication
# 1.1 Image mode # 1.1 Case : there is a message to display
if ( $req->{error} == PE_IMG_OK || $req->{error} == PE_IMG_NOK ) { if ( my $info = $req->info() ) {
$self->lmLog( 'Request for file', 'debug' );
return staticFile( "common/"
. ( $req->{error} == PE_IMG_OK ? 'ok.png' : 'warning.png' ) );
}
# 1.2 Case : there is a message to display
elsif ( my $info = $req->info() ) {
$skinfile = 'info'; $skinfile = 'info';
%templateParams = ( %templateParams = (
AUTH_ERROR_TYPE => $req->error_type, AUTH_ERROR_TYPE => $req->error_type,
@ -81,7 +74,7 @@ sub display {
); );
} }
# 1.3 Redirection # 1.2 Redirection
elsif ( $req->{error} == PE_REDIRECT ) { elsif ( $req->{error} == PE_REDIRECT ) {
$skinfile = "redirect"; $skinfile = "redirect";
%templateParams = ( %templateParams = (
@ -91,7 +84,7 @@ sub display {
); );
} }
# 1.4 Case : display menu # 1.3 Case : display menu
elsif ( $req->error == PE_OK ) { elsif ( $req->error == PE_OK ) {
$skinfile = 'menu'; $skinfile = 'menu';
@ -110,8 +103,8 @@ sub display {
# 2. Authentication not complete # 2. Authentication not complete
# 2.1 A notification has to be done (session is created but hidden and unusable # 2.1 A notification has to be done (session is created but hidden and
# until the user has accept the message) # unusable until the user has accept the message)
elsif ( my $notif = $req->datas->{notification} ) { elsif ( my $notif = $req->datas->{notification} ) {
$skinfile = 'notification'; $skinfile = 'notification';
%templateParams = ( %templateParams = (
@ -344,12 +337,12 @@ sub display {
# @param $type The content-type to use (ie: image/png) # @param $type The content-type to use (ie: image/png)
# @return void # @return void
sub staticFile { sub staticFile {
my ( $self, $file, $type ) = @_; my ( $self, $req, $file, $type ) = @_;
require Plack::Util; require Plack::Util;
require Cwd; require Cwd;
require HTTP::Date; require HTTP::Date;
open my $fh, '<:raw', $self->conf->{templatesDir} . "/$file" open my $fh, '<:raw', $self->conf->{templatesDir} . "/$file"
or return $self->sendError( $!, 403 ); or return $self->sendError( $req, $!, 403 );
my @stat = stat $file; my @stat = stat $file;
Plack::Util::set_io_path( $fh, Cwd::realpath($file) ); Plack::Util::set_io_path( $fh, Cwd::realpath($file) );
return [ return [

View File

@ -127,7 +127,9 @@ sub loginInfo {
} }
sub info { sub info {
print STDERR "TODO Request::info()\n"; my ( $self, $info ) = @_;
$self->datas->{_info} .= $info if ( defined $info );
return $self->datas->{_info};
} }
# TODO: oldpassword # TODO: oldpassword

View File

@ -583,8 +583,8 @@ sub getFirstValue {
} }
sub info { sub info {
my($self,$req,$info)=@_; my ( $self, $req, $info ) = @_;
print STDERR "####### TODO: info()\n"; return $req->info($info);
} }
1; 1;

View File

@ -7,7 +7,7 @@ BEGIN {
require 't/test-lib.pm'; require 't/test-lib.pm';
} }
my $maintests = 28; my $maintests = 26;
my $debug = 'error'; my $debug = 'error';
my ( $issuer, $sp, $res ); my ( $issuer, $sp, $res );
my %handlerOR = ( issuer => [], sp => [] ); my %handlerOR = ( issuer => [], sp => [] );
@ -220,7 +220,7 @@ sub LWP::UserAgent::request {
$httpResp->header( $name, shift( @{ $res->[1] } ) ); $httpResp->header( $name, shift( @{ $res->[1] } ) );
} }
$httpResp->content( join( '', @{ $res->[2] } ) ); $httpResp->content( join( '', @{ $res->[2] } ) );
count(3); count(4);
return $httpResp; return $httpResp;
} }

View File

@ -7,7 +7,7 @@ BEGIN {
require 't/test-lib.pm'; require 't/test-lib.pm';
} }
my $maintests = 18; my $maintests = 21;
my $debug = 'debug'; my $debug = 'debug';
my ( $issuer, $sp, $res ); my ( $issuer, $sp, $res );
my %handlerOR = ( issuer => [], sp => [] ); my %handlerOR = ( issuer => [], sp => [] );
@ -104,17 +104,65 @@ SKIP: {
cookie => "lemonldap=$idpId", cookie => "lemonldap=$idpId",
accept => 'text/html' accept => 'text/html'
), ),
'Query SP for logout' 'Query IdP for logout'
); );
ok( $res->[0] == 200, 'Return code is 200' ); ok( $res->[0] == 200, 'Return code is 200' );
#print STDERR Dumper($res); ok(
$res->[2]->[0] =~
m#img src="http://auth.idp.com(/saml/relaySingleLogoutSOAP)\?(relay=.*?)"#s,
'Get image request'
);
ok(
$res = $issuer->_get(
$1,
query => $2,
#cookie => "lemonldap=$idpId",
accept => 'text/html'
),
'Get image'
);
ok( $issuer->getHeader( $res, 'Content-Type' ) eq 'image/png',
'Get an image' );
} }
count($maintests); count($maintests);
clean_sessions(); clean_sessions();
done_testing( count() ); done_testing( count() );
# Redefine LWP methods for tests
sub LWP::UserAgent::request {
my ( $self, $req ) = @_;
ok( $req->uri =~ m#http://auth.sp.com(.*)#, 'Request from SP to IdP' );
my $url = $1;
my $res;
my $s = $req->content;
ok(
$res = $sp->_post(
$url, IO::String->new($s),
length => length($s),
type => 'application/xml',
),
'Execute request'
);
#ok( ( $res->[0] == 200 or $res->[0] == 400 ), 'Response is 200 or 400' )
# or explain( $res->[0], "200 or 400" );
#ok( $issuer->getHeader( $res, 'Content-Type' ) =~ m#^application/xml#,
# 'Content is XML' )
# or explain( $res->[1], 'Content-Type => application/xml' );
my $httpResp = HTTP::Response->new( $res->[0], 'OK' );
while ( my $name = shift @{ $res->[1] } ) {
$httpResp->header( $name, shift( @{ $res->[1] } ) );
}
$httpResp->content( join( '', @{ $res->[2] } ) );
count(2);
return $httpResp;
}
sub switch { sub switch {
my $type = shift; my $type = shift;
@Lemonldap::NG::Handler::Main::Reload::_onReload = @{ @Lemonldap::NG::Handler::Main::Reload::_onReload = @{
@ -127,6 +175,7 @@ sub issuer {
{ {
ini => { ini => {
logLevel => $debug, logLevel => $debug,
templatesDir => 'site/htdocs/static',
domain => 'idp.com', domain => 'idp.com',
portal => 'http://auth.idp.com', portal => 'http://auth.idp.com',
authentication => 'Demo', authentication => 'Demo',