From 423541455bc930b6069340ac6dfef609091bfb07 Mon Sep 17 00:00:00 2001 From: Thomas CHEMINEAU Date: Thu, 29 Apr 2010 13:39:26 +0000 Subject: [PATCH] SAML: - Manage SOAP relay logout request; - Fix a bug into info.tpl. --- .../lemonldap-ng-portal/example/index_skin.pl | 16 ++++- .../example/skins/impact/info.tpl | 1 + .../example/skins/pastel/info.tpl | 1 + .../lib/Lemonldap/NG/Portal/IssuerDBSAML.pm | 61 +++++++++++++++++-- .../lib/Lemonldap/NG/Portal/Simple.pm | 31 ++++++++-- .../lib/Lemonldap/NG/Portal/_SAML.pm | 61 +++++++++++-------- 6 files changed, 133 insertions(+), 38 deletions(-) diff --git a/modules/lemonldap-ng-portal/example/index_skin.pl b/modules/lemonldap-ng-portal/example/index_skin.pl index 1f967ef1a..47c3c55fa 100755 --- a/modules/lemonldap-ng-portal/example/index_skin.pl +++ b/modules/lemonldap-ng-portal/example/index_skin.pl @@ -47,8 +47,18 @@ my ( $skinfile, %templateParams ); if ( $portal->process() ) { - # 1.1 Case : there is a message to display - if ( my $info = $portal->info() ) { + # 1.1 Image mode + if ( $portal->{error} == PE_INFO_OK || $portal->{error} == PE_INFO_KO ) { + $skinfile = $skin_dir . '/common/ok.png'; + if ( $portal->{error} == PE_INFO_KO ) { + $skinfile = $skin_dir . '/common/warning.png'; + } + $portal->printImage($skinfile, 'image/png'); + exit; + } + + # 1.2 Case : there is a message to display + elsif ( my $info = $portal->info() ) { $skinfile = 'info.tpl'; %templateParams = ( AUTH_ERROR_TYPE => $portal->error_type, @@ -59,7 +69,7 @@ if ( $portal->process() ) { ); } - # 1.2 Case : display menu + # 1.3 Case : display menu else { $skinfile = 'menu.tpl'; diff --git a/modules/lemonldap-ng-portal/example/skins/impact/info.tpl b/modules/lemonldap-ng-portal/example/skins/impact/info.tpl index fb0458715..7e0374fb0 100644 --- a/modules/lemonldap-ng-portal/example/skins/impact/info.tpl +++ b/modules/lemonldap-ng-portal/example/skins/impact/info.tpl @@ -31,6 +31,7 @@ function stop() { _go=0; $('#timer').html("..."); + $('#form button[type=submit]').attr('disabled',''); } function go() { if(_go) { diff --git a/modules/lemonldap-ng-portal/example/skins/pastel/info.tpl b/modules/lemonldap-ng-portal/example/skins/pastel/info.tpl index 5c27650bc..6bf900606 100644 --- a/modules/lemonldap-ng-portal/example/skins/pastel/info.tpl +++ b/modules/lemonldap-ng-portal/example/skins/pastel/info.tpl @@ -29,6 +29,7 @@ function stop() { _go=0; $('#timer').html("..."); + $('#form button[type=submit]').attr('disabled',''); } function go() { if(_go) { diff --git a/modules/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/IssuerDBSAML.pm b/modules/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/IssuerDBSAML.pm index 8f08d477a..943260004 100644 --- a/modules/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/IssuerDBSAML.pm +++ b/modules/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/IssuerDBSAML.pm @@ -55,6 +55,8 @@ sub issuerForUnAuthUser { $self->getMetaDataURL( "samlIDPSSODescriptorSingleLogoutServiceHTTP", 2 ); my $saml_ars_url = $self->getMetaDataURL( "samlIDPSSODescriptorArtifactResolutionServiceArtifact"); + my $saml_slo_url_relay_soap = $self->{portal} + . '/saml/relaySingleLogoutSOAP'; # Get HTTP request informations to know # if we are receving SAML request or response @@ -173,15 +175,64 @@ sub issuerForUnAuthUser { } # 1.3. SLO SOAP replay (send SOAP requests asynchronously) - # http://auth.example.com/saml/relaySingleLogoutSOAP - my $saml_slo_url_relay_soap = - 'http://auth.example.com/saml/relaySingleLogoutSOAP'; if ( $url =~ /^(\Q$saml_slo_url_relay_soap\E)/i ) { $self->lmLog( "URL $url detected as a relay service URL", 'debug' ); - my $r = $self->param('relay'); - print STDERR "$r\n"; + # Check if relay parameter is present (mandatory) + my $samlID; + unless ( $samlID = $self->param('relay') ) { + $self->lmLog( "No relayID detected", 'error' ); + return PE_ERROR; + } + + # Retrieve the corresponding data from samlStorage + my $samlData = $self->replayProtection($samlID); + unless ( $samlData && $samlData ne 1 ) { + $self->lmLog( "No logout dump found for samlID $samlID", 'error' ); + return PE_ERROR; + } + + # Rebuild the logout object + my $logout; + unless ( $logout = $self->createLogout($server) ) { + $self->lmLog( "Could not rebuild logout object", + 'error' ); + return PE_ERROR; + } + + # Load Session and Identity if they exist + my $session = $samlData->{_lassoSessionDump}; + my $identity = $samlData->{_lassoIdentityDump}; + my $providerID = $samlData->{_providerID}; + + if ($session) { + unless ( $self->setSessionFromDump( $logout, $session ) ) { + $self->lmLog( "Unable to load Lasso Session", 'error' ); + return PE_ERROR; + } + $self->lmLog( "Lasso Session loaded", 'debug' ); + } + + if ($identity) { + unless ( $self->setIdentityFromDump( $logout, $identity ) ) { + $self->lmLog( "Unable to load Lasso Identity", 'error' ); + return PE_ERROR; + } + $self->lmLog( "Lasso Identity loaded", 'debug' ); + } + + # Send the logout request + my ( $rstatus, $rmethod, $rinfo ) = + $self->sendLogoutRequestToServiceProvider( + $logout, $providerID, Lasso::Constants::HTTP_METHOD_SOAP ); + unless ($rstatus) { + $self->lmLog( "Fail to process SOAP logout request to $providerID", + 'error' ); + return PE_INFO_KO; + } + + return PE_INFO_OK; } diff --git a/modules/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/Simple.pm b/modules/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/Simple.pm index 821f13f22..64dd4922b 100644 --- a/modules/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/Simple.pm +++ b/modules/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/Simple.pm @@ -51,6 +51,8 @@ use constant { # Portal errors # Developers warning, do not use PE_INFO, it's reserved to autoRedirect. # If you want to send an information, use $self->info('text'). + PE_INFO_KO => -5, + PE_INFO_OK => -4, PE_INFO => -3, PE_REDIRECT => -2, PE_DONE => -1, @@ -109,10 +111,10 @@ use constant { }; # EXPORTER PARAMETERS -our @EXPORT = qw( PE_INFO PE_REDIRECT PE_DONE PE_OK PE_SESSIONEXPIRED - PE_FORMEMPTY PE_WRONGMANAGERACCOUNT PE_USERNOTFOUND PE_BADCREDENTIALS - PE_LDAPCONNECTFAILED PE_LDAPERROR PE_APACHESESSIONERROR PE_FIRSTACCESS - PE_BADCERTIFICATE PE_PP_ACCOUNT_LOCKED PE_PP_PASSWORD_EXPIRED +our @EXPORT = qw( PE_INFO_KO PE_INFO_OK PE_INFO PE_REDIRECT PE_DONE PE_OK + PE_SESSIONEXPIRED PE_FORMEMPTY PE_WRONGMANAGERACCOUNT PE_USERNOTFOUND + PE_BADCREDENTIALS PE_LDAPCONNECTFAILED PE_LDAPERROR PE_APACHESESSIONERROR + PE_FIRSTACCESS PE_BADCERTIFICATE PE_PP_ACCOUNT_LOCKED PE_PP_PASSWORD_EXPIRED PE_CERTIFICATEREQUIRED PE_ERROR PE_PP_CHANGE_AFTER_RESET PE_PP_PASSWORD_MOD_NOT_ALLOWED PE_PP_MUST_SUPPLY_OLD_PASSWORD PE_PP_INSUFFICIENT_PASSWORD_QUALITY PE_PP_PASSWORD_TOO_SHORT @@ -787,6 +789,27 @@ sub info { return $self->{_info}; } +##@method public void printImage(string file, string type) +# Print image to STDOUT +# @param $file The path to the file to print +# @param $type The content-type to use (ie: image/png) +# @return void +sub printImage { + my ( $self, $file, $type ) = @_; + binmode STDOUT; + unless (open(IMAGE, '<', $file)) { + $self->lmLog( "Could not display image '$file'", 'error' ); + return; + } + print $self->header( + $type . '; charset=utf-8; content-length=' . (stat($file))[10]); + my $buffer = ""; + while (read(IMAGE, $buffer, 4096)) { + print $buffer; + } + close(IMAGE); +} + ############################################################### # MAIN subroutine: call all steps until one returns something # # different than PE_OK # diff --git a/modules/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/_SAML.pm b/modules/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/_SAML.pm index 0c5889615..32d38bf1b 100644 --- a/modules/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/_SAML.pm +++ b/modules/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/_SAML.pm @@ -1360,6 +1360,7 @@ sub replayProtection { # A session was found foreach (@keys) { my $session = $_; + my $result = 1; # Delete it eval { @@ -1372,6 +1373,9 @@ sub replayProtection { ); return 0; } + if (defined $h{data}) { + $result = $h{data}; + } eval { tied(%h)->delete(); }; if ($@) { $self->lmLog( @@ -1383,7 +1387,7 @@ sub replayProtection { $self->lmLog( "Assertion session $session (Message ID $samlID) was deleted", 'debug' ); - return 1; + return $result; } } @@ -2117,10 +2121,16 @@ sub sendLogoutRequestToServiceProvider { if ( !$method ) { $method = $self->getFirstHttpMethod( $server, $providerID, $protocolType ); - - #$method = Lasso::Constants::HTTP_METHOD_SOAP; } + # Fix a default value for the relay parameter + $relay = 0 unless ( defined $relay ); + + # Build the request unless this is a SOAP relay logout request + unless ( $method == Lasso::Constants::HTTP_METHOD_SOAP && $relay ) { + + $self->lmLog( "No logout request found, build it", 'debug' ); + # Initiate the logout request unless ( $self->initLogoutRequest( $logout, $providerID, $method ) ) { $self->lmLog( "Initiate logout request failed for $providerID", @@ -2134,6 +2144,8 @@ sub sendLogoutRequestToServiceProvider { return ( 0, $method, undef ); } + } + # Send logout request to the provider depending of the request method # HTTP-REDIRECT if ( $method == Lasso::Constants::HTTP_METHOD_REDIRECT ) { @@ -2181,44 +2193,41 @@ sub sendLogoutRequestToServiceProvider { # HTTP-SOAP if ( $method == Lasso::Constants::HTTP_METHOD_SOAP ) { - # Fix a default value for the relay parameter - $relay = 1 unless ( defined $relay ); - # Build a relay request, to be used after SLO process is done if ($relay) { - $self->lmLog( "Store SOAP logout request for $providerID", + $self->lmLog( "Build SOAP relay logout request for $providerID", 'debug' ); - my $samlID = $logout->request()->ID; my $random = new String::Random; - my $cookieName = - $self->{cookieName} . '-r' - . $random->randregex('[A-Z]{3}[a-z]{5}.\d{2}'); + my $samlID = $random->randregex('[a-z0-9]{32}'); + + # Build needed information to be stored into samlStorage + my $samlData = (); + unless ( $logout->get_session() && $logout->get_identity() ) { + $self->lmLog( + "No session and identity found into logout object", + 'error' ); + return ( 0, $method, undef ); + } + $samlData->{_lassoSessionDump} = $logout->get_session->dump; + $samlData->{_lassoIdentityDump} = $logout->get_identity->dump; + $samlData->{_providerID} = $providerID; # Store information in temporary storage, to be reused then. return ( 0, $method, undef ) - unless ( $self->storeReplayProtection( $samlID, $logout->dump ) ); + unless ( $self->storeReplayProtection( $samlID, $samlData ) ); - # Build the cookie that will be used to retrieve this SOAP request. - push @{ $self->{cookie} }, - $self->cookie( - -name => $cookieName, - -value => $samlID, - -expires => '+3m' - ); + $self->lmLog( "Store request for $providerID", 'debug' ); # Build the URL that could be used to play this logout request - my $slo_url = - 'http://auth.example.com/saml/relaySingleLogoutSOAP?relay=' - . $cookieName; + my $slo_url = $self->{portal} + . '/saml/relaySingleLogoutSOAP?relay=' . $samlID; # Display information to the user $info .= '
  • ' - . $providerName . '...' - . '
  • '; + . $providerName . '...  ' + . ''; }