';for(f=0,p=v.length;f Get default lang"),k=window.availableLanguages[0]),console.log("Selected lang ->",k),L&&(console.log("Set cookie lang ->",k),G("llnglanguage",k)),N(k)):(console.log("Selected lang ->",s),G("llnglanguage",s),N(s)),r="",b=0,g=(O=window.availableLanguages).length;b ';return $("#languages").html(r),$(".langicon").on("click",function(){return s=$(this).attr("title"),G("llnglanguage",s),N(s)}),d=function(e){e=e.charCodeAt(0);return 47=window.datas.ppolicy.minsize?($("#ppolicy-minsize-feedback").addClass("fa-check text-success"),$("#ppolicy-minsize-feedback").removeClass("fa-times text-danger")):($("#ppolicy-minsize-feedback").removeClass("fa-check text-success"),$("#ppolicy-minsize-feedback").addClass("fa-times text-danger"),c=!1)),0=window.datas.ppolicy.minupper?($("#ppolicy-minupper-feedback").addClass("fa-check text-success"),$("#ppolicy-minupper-feedback").removeClass("fa-times text-danger")):($("#ppolicy-minupper-feedback").removeClass("fa-check text-success"),$("#ppolicy-minupper-feedback").addClass("fa-times text-danger"),c=!1)),0=window.datas.ppolicy.minlower?($("#ppolicy-minlower-feedback").addClass("fa-check text-success"),$("#ppolicy-minlower-feedback").removeClass("fa-times text-danger")):($("#ppolicy-minlower-feedback").removeClass("fa-check text-success"),$("#ppolicy-minlower-feedback").addClass("fa-times text-danger"),c=!1)),0=window.datas.ppolicy.mindigit?($("#ppolicy-mindigit-feedback").addClass("fa-check text-success"),$("#ppolicy-mindigit-feedback").removeClass("fa-times text-danger")):($("#ppolicy-mindigit-feedback").removeClass("fa-check text-success"),$("#ppolicy-mindigit-feedback").addClass("fa-times text-danger"),c=!1)),window.datas.ppolicy.allowedspechar){for(s=window.datas.ppolicy.allowedspechar.replace(/\s/g,""),a=!1,t=0,n=e.length;t=window.datas.ppolicy.minspechar?($("#ppolicy-minspechar-feedback").addClass("fa-check text-success"),$("#ppolicy-minspechar-feedback").removeClass("fa-times text-danger")):($("#ppolicy-minspechar-feedback").removeClass("fa-check text-success"),$("#ppolicy-minspechar-feedback").addClass("fa-times text-danger"),c=!1)}if(0=window.datas.ppolicy.minspechar?($("#ppolicy-minspechar-feedback").addClass("fa-check text-success"),$("#ppolicy-minspechar-feedback").removeClass("fa-times text-danger")):($("#ppolicy-minspechar-feedback").removeClass("fa-check text-success"),$("#ppolicy-minspechar-feedback").addClass("fa-times text-danger"),c=!1)}c?($(".ppolicy").removeClass("border-danger").addClass("border-success"),null!=(i=$("#newpassword").get(0))&&i.setCustomValidity("")):($(".ppolicy").removeClass("border-success").addClass("border-danger"),null!=(i=$("#newpassword").get(0))&&i.setCustomValidity(U("PE28")))},null!=window.datas.ppolicy&&$("#newpassword").length&&(t(""),$("#newpassword").keyup(function(e){t(e.target.value)})),L=function(e){return e.target.checked?($("#newpassword").off("keyup"),null!=(e=$("#newpassword").get(0))?e.setCustomValidity(""):void 0):($("#newpassword").keyup(function(e){t(e.target.value)}),t(""))},k=function(){var e,a;return(null!=(e=$("#confirmpassword").get(0))?e.value:void 0)===(null!=(e=$("#newpassword").get(0))?e.value:void 0)?(null!=(a=$("#confirmpassword").get(0))&&a.setCustomValidity(""),!0):(null!=(a=$("#confirmpassword").get(0))&&a.setCustomValidity(U("PE34")),!1)},$("#newpassword").change(k),$("#confirmpassword").change(k),null!=window.datas.ppolicy&&$("#newpassword").length&&$("#reset").change(L),S.pingInterval&&0 div.category",update:G}),I(),$("div.message").fadeIn("slow"),$("input[name=timezone]").val(-(new Date).getTimezoneOffset()/60),m=$("#menu").tabs({active:0}),(w=$('#menu a[href="#'+P.displaytab+'"]').parent().index())<0&&(w=0),m.tabs("option","active",w),a=$("#authMenu").tabs({active:0}),(m=$('#authMenu a[href="#'+P.displaytab+'"]').parent().index())<0&&(m=0),a.tabs("option","active",m),P.choicetab&&a.tabs("option","active",$('#authMenu a[href="#'+P.choicetab+'"]').parent().index()),P.login?$("input[type=password]:first").focus():0===$("input[autofocus]").length&&$("input[type!=hidden]:first").focus(),P.newwindow&&$("#appslist a").attr("target","_blank"),$("p.removeOther").length&&(w=$("#form").attr("action"),m=$("#form").attr("method"),console.log("method=",m),o="",$("#form input[type=hidden]")&&(console.log("Parse hidden values"),$("#form input[type=hidden]").each(function(e){return console.log(" ->",$(this).attr("name"),$(this).val()),o+="&"+$(this).attr("name")+"="+$(this).val()})),a="",w&&(console.log("action=",w),-1!==w.indexOf("?")?w.substring(0,w.indexOf("?")):a=w+"?",a+=o,o=""),m=$("p.removeOther a").attr("href")+"&method="+m+o,a&&(m+="&url="+btoa(a)),$("p.removeOther a").attr("href",m)),window.location.search&&((k=j("llnglanguage"))&&console.log("Get lang from parameter"),1===(O=j("setCookieLang"))&&console.log("Set lang cookie")),n||(n=L("llnglanguage"))&&!k&&console.log("Get lang from cookie"),n)R.call(window.availableLanguages,n)<0&&(n=window.availableLanguages[0],k||console.log("Lang not available -> Get default lang"));else if(navigator){for(i=[],l=[],v=[navigator.language],navigator.languages&&(v=navigator.languages),s=0,c=(C=window.availableLanguages).length;s ';for(g=0,p=v.length;g Get default lang"),k=window.availableLanguages[0]),console.log("Selected lang ->",k),O&&(console.log("Set cookie lang ->",k),D("llnglanguage",k)),N(k)):(console.log("Selected lang ->",n),D("llnglanguage",n),N(n)),r="",b=0,f=(T=window.availableLanguages).length;b ';return $("#languages").html(r),$(".langicon").on("click",function(){return n=$(this).attr("title"),D("llnglanguage",n),N(n)}),d=function(e){e=e.charCodeAt(0);return 47=window.datas.ppolicy.minsize?($("#ppolicy-minsize-feedback").addClass("fa-check text-success"),$("#ppolicy-minsize-feedback").removeClass("fa-times text-danger")):($("#ppolicy-minsize-feedback").removeClass("fa-check text-success"),$("#ppolicy-minsize-feedback").addClass("fa-times text-danger"),c=!1)),0=window.datas.ppolicy.minupper?($("#ppolicy-minupper-feedback").addClass("fa-check text-success"),$("#ppolicy-minupper-feedback").removeClass("fa-times text-danger")):($("#ppolicy-minupper-feedback").removeClass("fa-check text-success"),$("#ppolicy-minupper-feedback").addClass("fa-times text-danger"),c=!1)),0=window.datas.ppolicy.minlower?($("#ppolicy-minlower-feedback").addClass("fa-check text-success"),$("#ppolicy-minlower-feedback").removeClass("fa-times text-danger")):($("#ppolicy-minlower-feedback").removeClass("fa-check text-success"),$("#ppolicy-minlower-feedback").addClass("fa-times text-danger"),c=!1)),0=window.datas.ppolicy.mindigit?($("#ppolicy-mindigit-feedback").addClass("fa-check text-success"),$("#ppolicy-mindigit-feedback").removeClass("fa-times text-danger")):($("#ppolicy-mindigit-feedback").removeClass("fa-check text-success"),$("#ppolicy-mindigit-feedback").addClass("fa-times text-danger"),c=!1)),window.datas.ppolicy.allowedspechar){for(n=window.datas.ppolicy.allowedspechar.replace(/\s/g,""),a=!1,t=0,o=e.length;t=window.datas.ppolicy.minspechar?($("#ppolicy-minspechar-feedback").addClass("fa-check text-success"),$("#ppolicy-minspechar-feedback").removeClass("fa-times text-danger")):($("#ppolicy-minspechar-feedback").removeClass("fa-check text-success"),$("#ppolicy-minspechar-feedback").addClass("fa-times text-danger"),c=!1)}if(0=window.datas.ppolicy.minspechar?($("#ppolicy-minspechar-feedback").addClass("fa-check text-success"),$("#ppolicy-minspechar-feedback").removeClass("fa-times text-danger")):($("#ppolicy-minspechar-feedback").removeClass("fa-check text-success"),$("#ppolicy-minspechar-feedback").addClass("fa-times text-danger"),c=!1)}c?($(".ppolicy").removeClass("border-danger").addClass("border-success"),null!=(i=$("#newpassword").get(0))&&i.setCustomValidity("")):($(".ppolicy").removeClass("border-success").addClass("border-danger"),null!=(i=$("#newpassword").get(0))&&i.setCustomValidity(U("PE28")))},null!=window.datas.ppolicy&&$("#newpassword").length&&(t(""),$("#newpassword").keyup(function(e){t(e.target.value)})),O=function(e){return e.target.checked?($("#newpassword").off("keyup"),null!=(e=$("#newpassword").get(0))?e.setCustomValidity(""):void 0):($("#newpassword").keyup(function(e){t(e.target.value)}),t(""))},k=function(){var e,a;return(null!=(e=$("#confirmpassword").get(0))?e.value:void 0)===(null!=(e=$("#newpassword").get(0))?e.value:void 0)?(null!=(a=$("#confirmpassword").get(0))&&a.setCustomValidity(""),!0):(null!=(a=$("#confirmpassword").get(0))&&a.setCustomValidity(U("PE34")),!1)},$("#newpassword").change(k),$("#confirmpassword").change(k),null!=window.datas.ppolicy&&$("#newpassword").length&&$("#reset").change(O),P.enablePasswordDisplay&&(P.dontStorePassword?($(".toggle-password").mousedown(function(){return $(this).toggleClass("fa-eye fa-eye-slash"),$("input[name=password]").attr("class","form-control")}),$(".toggle-password").mouseup(function(){if($(this).toggleClass("fa-eye fa-eye-slash"),$("input[name=password]").get(0).value)return $("input[name=password]").attr("class","form-control key")})):($(".toggle-password").mousedown(function(){return $(this).toggleClass("fa-eye fa-eye-slash"),$("input[name=password]").attr("type","text")}),$(".toggle-password").mouseup(function(){return $(this).toggleClass("fa-eye fa-eye-slash"),$("input[name=password]").attr("type","password")}))),P.pingInterval&&0
-
@@ -34,10 +33,13 @@
-
+
+
+
+
+
-
@@ -46,15 +48,22 @@
-
+
+
+
+
+
-
-
+
+
+
+
+
+
-
-
-
+
+
+
diff --git a/lemonldap-ng-portal/site/templates/common/script.tpl b/lemonldap-ng-portal/site/templates/common/script.tpl
index 19f678a24..3aeb105e2 100644
--- a/lemonldap-ng-portal/site/templates/common/script.tpl
+++ b/lemonldap-ng-portal/site/templates/common/script.tpl
@@ -37,7 +37,8 @@
"allowedspechar": "",
"minspechar": ""
},
- "enablePasswordDisplay":
+ "enablePasswordDisplay":,
+ "dontStorePassword":
}
diff --git a/lemonldap-ng-portal/t/01-EnablePasswordDisplay.t b/lemonldap-ng-portal/t/01-EnablePasswordDisplay.t
new file mode 100644
index 000000000..d3560d851
--- /dev/null
+++ b/lemonldap-ng-portal/t/01-EnablePasswordDisplay.t
@@ -0,0 +1,25 @@
+use Test::More;
+use strict;
+
+require 't/test-lib.pm';
+
+my $res;
+
+my $client = LLNG::Manager::Test->new( {
+ ini => {
+ logLevel => 'error',
+ 'portalEnablePasswordDisplay' => 1,
+ 'browsersDontStorePassword' => 1
+ }
+ }
+);
+
+ok( $res = $client->_get( '/', accept => 'text/html' ), 'Display portal' );
+ok( $res->[2]->[0] =~ m%%,
+ ' toggle password icon found' )
+ or print STDERR Dumper( $res->[2]->[0] );
+count(2);
+
+clean_sessions();
+
+done_testing( count() );
diff --git a/lemonldap-ng-portal/t/37-Logout-from-2-chained-SAML-SP-SOAP.t b/lemonldap-ng-portal/t/37-Logout-from-2-chained-SAML-SP-SOAP.t
new file mode 100644
index 000000000..d09bd9f2c
--- /dev/null
+++ b/lemonldap-ng-portal/t/37-Logout-from-2-chained-SAML-SP-SOAP.t
@@ -0,0 +1,458 @@
+use lib 'inc';
+use Test::More;
+use strict;
+use IO::String;
+use LWP::UserAgent;
+use LWP::Protocol::PSGI;
+use MIME::Base64;
+
+
+# ------------ ----------------------------- ----------------
+# | SAML SP | <-> |SAML IDP + SAML SP (proxy) | <-> | SAML IdP |
+# ------------ ----------------------------- ----------------
+#
+# Use case:
+# - login from SP up to SAML IdP
+# - logout asked from SP, and propagated up to SAML IdP
+# logout between all SAML SP and IdP is done with SOAP binding
+
+BEGIN {
+ require 't/test-lib.pm';
+ require 't/oidc-lib.pm';
+ require 't/saml-lib.pm';
+}
+
+my $maintests = 13;
+my $debug = 'error';
+my ( $sp, $proxy, $idp, $res );
+
+# Overloads method register, for enabling direct POST requests between SP, PROXY and IDP
+LWP::Protocol::PSGI->register(
+ sub {
+ my $req = Plack::Request->new(@_);
+ ok( $req->uri =~ m#http://auth.((?:sp|proxy|idp)).com(.*)#, ' REST request' );
+ my $host = $1;
+ my $url = $2;
+ my ( $res, $client );
+ count(1);
+ if ( $host eq 'sp' ) {
+ pass(" Request to SP, endpoint $url");
+ $client = $sp;
+ }
+ elsif ( $host eq 'proxy' ) {
+ pass(' Request from PROXY to PROXY');
+ $client = $proxy;
+ }
+ elsif ( $host eq 'idp' ) {
+ pass(' Request to IDP');
+ $client = $idp;
+ }
+ else {
+ fail(' Aborting REST request (external)');
+ return HTTP::Response->new(500);
+ }
+ if ( $req->method =~ /^post$/i ) {
+ my $s = $req->content;
+ ok(
+ $res = $client->_post(
+ $url, IO::String->new($s),
+ length => length($s),
+ type => $req->header('Content-Type'),
+ ),
+ ' Execute post request'
+ );
+ }
+ else {
+ ok(
+ $res = $client->_get(
+ $url,
+ custom => {
+ HTTP_AUTHORIZATION => $req->header('Authorization'),
+ }
+ ),
+ ' Execute get request'
+ );
+ }
+ ok( $res->[0] == 200, ' Response is 200' );
+ ok( getHeader( $res, 'Content-Type' ) =~ m#^(application/json|text/xml)#,
+ ' Content is JSON|XML' )
+ or explain( $res->[1], 'Content-Type => (application/json|text/xml)' );
+ count(4);
+ return $res;
+ }
+);
+
+
+
+SKIP: {
+ eval "use Lasso";
+ if ($@) {
+ skip 'Lasso not found', $maintests;
+ }
+
+ # Initialization
+ $idp = register( 'idp', \&idp );
+ $sp = register( 'sp', \&sp );
+ $proxy = register( 'proxy', \&proxy );
+
+
+ # LOGIN PROCESS ############################################################
+
+ # Query SP for auth
+ ok( $res = $sp->_get( '/', accept => 'text/html' ), 'Unauth SP request' );
+ my ( $url, $query ) =
+ expectRedirection( $res,
+ qr#http://auth.proxy.com(/saml/singleSignOn)\?(.*)$# );
+
+ # Push request to PROXY
+ switch ('proxy');
+ ok( $res = $proxy->_get( $url, query => $query, accept => 'text/html' ),
+ "Push request to PROXY, endpoint $url" );
+
+ my $pdataproxy = expectCookie( $res, 'lemonldappdata' );
+
+ my ( $urlidp, $queryidp ) =
+ expectRedirection( $res,
+ qr#http://auth.idp.com(/saml/singleSignOn)\?(.*)$# );
+
+ # Push request to IDP
+ switch ('idp');
+
+ # Try to authenticate to IdP
+ ok(
+ $res = $idp->_get( $urlidp, query => $queryidp, accept => 'text/html'),
+ "SAML Authentication on idp, endpoint $urlidp" );
+ my $pdataidp = expectCookie( $res, 'lemonldappdata' );
+
+ my ( $host, $tmp );
+ # expectForm (result, host, uri, @requiredfield)
+ ( $host, $tmp, $query ) = expectForm( $res, '#', undef,
+ ( 'url', 'timezone', 'skin', 'user', 'password' ) );
+ $query =~ s/user=/user=dwho/;
+ $query =~ s/password=/password=dwho/;
+
+ ok(
+ $res = $idp->_post(
+ $urlidp,
+ IO::String->new($query),
+ accept => 'text/html',
+ cookie => "lemonldappdata=$pdataidp",
+ length => length($query),
+ ),
+ "Post authentication, endpoint $urlidp"
+ );
+
+ $pdataidp = expectCookie( $res, 'lemonldappdata' );
+ my $cookieidp = expectCookie( $res, 'lemonldap' );
+
+ ( $host, $url, $query ) =
+ expectForm( $res, 'auth.proxy.com', '/saml/proxySingleSignOnPost',
+ 'SAMLResponse', 'RelayState' );
+
+ my ($resp) = $query =~ qr/SAMLResponse=([^&]*)/;
+
+ # Post SAML response to PROXY
+ switch ('proxy');
+ ok(
+ $res = $proxy->_post(
+ $url, IO::String->new($query),
+ accept => 'text/html',
+ length => length($query),
+ cookie => "lemonldappdata=$pdataproxy",
+ ),
+ 'Post SAML response to PROXY'
+ );
+
+ $pdataproxy = expectCookie( $res, 'lemonldappdata' );
+ my $cookieproxy = expectCookie( $res, 'lemonldap' );
+
+ ( $url, $query ) = expectRedirection( $res, qr#^http://auth.proxy.com(/saml)\?*(.*)$# );
+ ok(
+ $res = $proxy->_get( $url,
+ query => $query,
+ accept => 'text/html',
+ cookie => "lemonldappdata=$pdataproxy; lemonldap=$cookieproxy" ),
+ "internal redirection to PROXY, endpoint $url" );
+
+ ( $host, $url, $query ) =
+ expectForm( $res, 'auth.sp.com', '/saml/proxySingleSignOnPost', 'SAMLResponse' );
+
+ my ($resp) = $query =~ qr/SAMLResponse=([^&]*)/;
+
+ # Post SAML response to PROXY
+ switch ('sp');
+ ok(
+ $res = $sp->_post(
+ $url, IO::String->new($query),
+ accept => 'text/html',
+ length => length($query),
+ ),
+ 'Post SAML response to SP'
+ );
+
+ my $cookiesp = expectCookie( $res, 'lemonldap' );
+
+
+ # Authentication done on SP + PROXY + IDP
+
+
+ # LOGOUT PROCESS ###########################################################
+ $url = '/';
+ $query = 'logout=1';
+ ok( $res = $sp->_get( $url, query => $query,
+ accept => 'text/html',
+ cookie => "lemonldap=$cookiesp",
+ ),
+ 'Call logout from SP' );
+
+ # lemonldap cookie set to "0"
+ $cookiesp = expectCookie( $res, 'lemonldap' );
+ ok( $cookiesp eq "0", 'Test empty cookie on SP' );
+
+ ok ( $res->[2]->[0] =~ /trmsg="-7"/, 'Test disconnexion message on SP' );
+
+
+ # test connexion on PROXY
+ switch('proxy');
+ ok( $res = $proxy->_get( '/', query => '',
+ accept => 'text/html',
+ cookie => "lemonldap=$cookieproxy",
+ ),
+ 'Test if still logged on PROXY' );
+
+ my ( $urlidp, $queryidp ) =
+ expectRedirection( $res,
+ qr#http://auth.idp.com(/saml/singleSignOn)\?(.*)$# );
+
+ # test connexion on IDP
+ switch('idp');
+ ok( $res = $idp->_get( '/', query => '',
+ accept => 'text/html',
+ cookie => "lemonldap=$cookieidp",
+ ),
+ 'Test if still logged on IDP' );
+
+ like( $res->[2]->[0], qr/userfield/,
+ 'test presence of user field in form (prove successful logout)' );
+
+}
+
+count($maintests);
+clean_sessions();
+done_testing( count() );
+
+sub proxy {
+ return LLNG::Manager::Test->new( {
+ ini => {
+ logLevel => $debug,
+ domain => 'proxy.com',
+ portal => 'http://auth.proxy.com',
+ authentication => 'SAML',
+ userDB => 'Same',
+ issuerDBOpenIDConnectActivation => "1",
+
+ samlOrganizationDisplayName => "proxy",
+ samlOrganizationName => "proxy",
+ samlOrganizationURL => "http://www.proxy.com/",
+ samlServicePrivateKeyEnc => saml_key_proxy_private_enc,
+ samlServicePrivateKeySig => saml_key_proxy_private_sig,
+ samlServicePublicKeyEnc => saml_key_proxy_public_enc,
+ samlServicePublicKeySig => saml_key_proxy_public_sig,
+ samlIDPSSODescriptorWantAuthnRequestsSigned => 1,
+ samlSPSSODescriptorWantAssertionsSigned => 1,
+ samlIDPMetaDataXML => {
+ 'idp' => {
+ samlIDPMetaDataXML =>
+ samlIDPComplexMetaDataXML( 'idp', 'HTTP-Redirect', 'SOAP' )
+ },
+ },
+ samlIDPMetaDataOptions => {
+ 'idp' => {
+ 'samlIDPMetaDataOptionsAdaptSessionUtime' => 0,
+ 'samlIDPMetaDataOptionsAllowLoginFromIDP' => 0,
+ 'samlIDPMetaDataOptionsAllowProxiedAuthn' => 0,
+ 'samlIDPMetaDataOptionsCheckAudience' => 1,
+ 'samlIDPMetaDataOptionsCheckSLOMessageSignature' => 1,
+ 'samlIDPMetaDataOptionsCheckSSOMessageSignature' => 1,
+ 'samlIDPMetaDataOptionsCheckTime' => 1,
+ 'samlIDPMetaDataOptionsDisplayName' => 'idp',
+ 'samlIDPMetaDataOptionsEncryptionMode' => 'none',
+ 'samlIDPMetaDataOptionsForceAuthn' => 0,
+ 'samlIDPMetaDataOptionsForceUTF8' => 0,
+ 'samlIDPMetaDataOptionsIcon' => '',
+ 'samlIDPMetaDataOptionsIsPassive' => 0,
+ 'samlIDPMetaDataOptionsNameIDFormat' => '',
+ 'samlIDPMetaDataOptionsRelayStateURL' => 0,
+ 'samlIDPMetaDataOptionsRequestedAuthnContext' => '',
+ 'samlIDPMetaDataOptionsResolutionRule' => '',
+ 'samlIDPMetaDataOptionsSLOBinding' => 'http-soap',
+ 'samlIDPMetaDataOptionsSSOBinding' => 'http-redirect',
+ 'samlIDPMetaDataOptionsSignSLOMessage' => 1,
+ 'samlIDPMetaDataOptionsSignSSOMessage' => 1,
+ 'samlIDPMetaDataOptionsSignatureMethod' => '',
+ 'samlIDPMetaDataOptionsStoreSAMLToken' => 0
+ }
+ },
+ samlIDPMetaDataExportedAttributes => {
+ 'idp' => {
+ 'cn' => '1;cn',
+ 'uid' => '1;uid',
+ 'mail' => '1;mail',
+ }
+ },
+
+ issuerDBSAMLActivation => 1,
+ restSessionServer => 1,
+ samlSPMetaDataOptions => {
+ sp => {
+ 'samlSPMetaDataOptionsCheckSLOMessageSignature' => 1,
+ 'samlSPMetaDataOptionsCheckSSOMessageSignature' => 1,
+ 'samlSPMetaDataOptionsEnableIDPInitiatedURL' => 0,
+ 'samlSPMetaDataOptionsEncryptionMode' => 'none',
+ 'samlSPMetaDataOptionsForceUTF8' => 1,
+ 'samlSPMetaDataOptionsNameIDFormat' => '',
+ 'samlSPMetaDataOptionsNotOnOrAfterTimeout' => 72000,
+ 'samlSPMetaDataOptionsOneTimeUse' => 0,
+ 'samlSPMetaDataOptionsSessionNotOnOrAfterTimeout' => 72000,
+ 'samlSPMetaDataOptionsSignSLOMessage' => -1,
+ 'samlSPMetaDataOptionsSignSSOMessage' => 1,
+ 'samlSPMetaDataOptionsSignatureMethod' => ''
+ }
+ },
+ samlSPMetaDataXML => {
+ sp => {
+ samlSPMetaDataXML =>
+ samlSPComplexMetaDataXML( 'sp', 'HTTP-Redirect', 'SOAP' ),
+ 'samlSPSSODescriptorAuthnRequestsSigned' => 1,
+ 'samlSPSSODescriptorWantAssertionsSigned' => 1,
+ }
+ },
+ samlSPMetaDataExportedAttributes => {
+ 'sp' => {
+ 'cn' => '1;cn',
+ 'uid' => '1;uid',
+ 'mail' => '1;mail',
+ }
+ },
+ samlSPSSODescriptorAuthnRequestsSigned => 1,
+ },
+ }
+ );
+}
+
+sub sp {
+ return LLNG::Manager::Test->new( {
+ ini => {
+ logLevel => $debug,
+ domain => 'sp.com',
+ portal => 'http://auth.sp.com',
+ authentication => 'SAML',
+ userDB => 'Same',
+ issuerDBOpenIDConnectActivation => "1",
+ samlOrganizationDisplayName => "SP",
+ samlOrganizationName => "SP",
+ samlOrganizationURL => "http://www.sp.com/",
+ samlServicePrivateKeyEnc => saml_key_sp_private_enc,
+ samlServicePrivateKeySig => saml_key_sp_private_sig,
+ samlServicePublicKeyEnc => saml_key_sp_public_enc,
+ samlServicePublicKeySig => saml_key_sp_public_sig,
+ samlIDPSSODescriptorWantAuthnRequestsSigned => 1,
+ samlSPSSODescriptorWantAssertionsSigned => 1,
+ samlIDPMetaDataXML => {
+ 'proxy' => {
+ samlIDPMetaDataXML =>
+ samlProxyComplexMetaDataXML( 'proxy', 'HTTP-Redirect', 'SOAP' )
+ },
+ },
+ samlIDPMetaDataOptions => {
+ 'proxy' => {
+ 'samlIDPMetaDataOptionsAdaptSessionUtime' => 0,
+ 'samlIDPMetaDataOptionsAllowLoginFromIDP' => 0,
+ 'samlIDPMetaDataOptionsAllowProxiedAuthn' => 0,
+ 'samlIDPMetaDataOptionsCheckAudience' => 1,
+ 'samlIDPMetaDataOptionsCheckSLOMessageSignature' => 1,
+ 'samlIDPMetaDataOptionsCheckSSOMessageSignature' => 1,
+ 'samlIDPMetaDataOptionsCheckTime' => 1,
+ 'samlIDPMetaDataOptionsDisplayName' => 'proxy',
+ 'samlIDPMetaDataOptionsEncryptionMode' => 'none',
+ 'samlIDPMetaDataOptionsForceAuthn' => 0,
+ 'samlIDPMetaDataOptionsForceUTF8' => 0,
+ 'samlIDPMetaDataOptionsIcon' => '',
+ 'samlIDPMetaDataOptionsIsPassive' => 0,
+ 'samlIDPMetaDataOptionsNameIDFormat' => '',
+ 'samlIDPMetaDataOptionsRelayStateURL' => 0,
+ 'samlIDPMetaDataOptionsRequestedAuthnContext' => '',
+ 'samlIDPMetaDataOptionsResolutionRule' => '',
+ 'samlIDPMetaDataOptionsSLOBinding' => 'http-soap',
+ 'samlIDPMetaDataOptionsSSOBinding' => 'http-redirect',
+ 'samlIDPMetaDataOptionsSignSLOMessage' => 1,
+ 'samlIDPMetaDataOptionsSignSSOMessage' => 1,
+ 'samlIDPMetaDataOptionsSignatureMethod' => '',
+ 'samlIDPMetaDataOptionsStoreSAMLToken' => 0
+ }
+ },
+ samlIDPMetaDataExportedAttributes => {
+ 'proxy' => {
+ 'cn' => '1;cn',
+ 'uid' => '1;uid',
+ 'mail' => '1;mail',
+ }
+ },
+ }
+ }
+ );
+}
+
+sub idp {
+ return LLNG::Manager::Test->new( {
+ ini => {
+ logLevel => $debug,
+ domain => 'idp.com',
+ portal => 'http://auth.idp.com',
+ authentication => 'Demo',
+ userDB => 'Same',
+ issuerDBSAMLActivation => 1,
+ restSessionServer => 1,
+ samlSPMetaDataOptions => {
+ proxy => {
+ 'samlSPMetaDataOptionsCheckSLOMessageSignature' => 1,
+ 'samlSPMetaDataOptionsCheckSSOMessageSignature' => 1,
+ 'samlSPMetaDataOptionsEnableIDPInitiatedURL' => 0,
+ 'samlSPMetaDataOptionsEncryptionMode' => 'none',
+ 'samlSPMetaDataOptionsForceUTF8' => 1,
+ 'samlSPMetaDataOptionsNameIDFormat' => '',
+ 'samlSPMetaDataOptionsNotOnOrAfterTimeout' => 72000,
+ 'samlSPMetaDataOptionsOneTimeUse' => 0,
+ 'samlSPMetaDataOptionsSessionNotOnOrAfterTimeout' => 72000,
+ 'samlSPMetaDataOptionsSignSLOMessage' => -1,
+ 'samlSPMetaDataOptionsSignSSOMessage' => 1,
+ 'samlSPMetaDataOptionsSignatureMethod' => ''
+ }
+ },
+ samlSPMetaDataXML => {
+ proxy => {
+ samlSPMetaDataXML =>
+ samlProxyComplexMetaDataXML( 'proxy', 'HTTP-Redirect', 'SOAP' ),
+ 'samlSPSSODescriptorAuthnRequestsSigned' => 1,
+ 'samlSPSSODescriptorWantAssertionsSigned' => 1,
+ }
+ },
+ samlSPMetaDataExportedAttributes => {
+ 'proxy' => {
+ 'cn' => '1;cn',
+ 'uid' => '1;uid',
+ 'mail' => '1;mail',
+ }
+ },
+ samlOrganizationDisplayName => "IDP",
+ samlOrganizationName => "IDP",
+ samlOrganizationURL => "http://www.idp.com",
+ samlServicePublicKeySig => saml_key_idp_public_sig,
+ samlServicePrivateKeyEnc => saml_key_idp_private_enc,
+ samlServicePrivateKeySig => saml_key_idp_private_sig,
+ samlServicePublicKeyEnc => saml_key_idp_public_enc,
+ samlSPSSODescriptorAuthnRequestsSigned => 1,
+ },
+ }
+ );
+}
diff --git a/lemonldap-ng-portal/t/37-Logout-from-OIDC-RP-to-SAML-IDP-Redirect.t b/lemonldap-ng-portal/t/37-Logout-from-OIDC-RP-to-SAML-IDP-Redirect.t
new file mode 100644
index 000000000..9db1125c0
--- /dev/null
+++ b/lemonldap-ng-portal/t/37-Logout-from-OIDC-RP-to-SAML-IDP-Redirect.t
@@ -0,0 +1,477 @@
+use lib 'inc';
+use Test::More;
+use strict;
+use IO::String;
+use LWP::UserAgent;
+use LWP::Protocol::PSGI;
+use MIME::Base64;
+
+
+# ------------ --------------------------- ----------------
+# | OIDC RP | <-> | OIDC provider + SAML SP | <-> | SAML IdP |
+# ------------ --------------------------- ----------------
+#
+# Use case:
+# - login from RP up to SAML IdP
+# - logout asked from RP, and propagated up to SAML IdP
+# logout between SAML SP and IdP is done with Redirect binding
+
+BEGIN {
+ require 't/test-lib.pm';
+ require 't/oidc-lib.pm';
+ require 't/saml-lib.pm';
+}
+
+my $maintests = 17;
+my $debug = 'error';
+#my $debug = 'error';
+my ( $op, $rp, $idp, $res );
+
+# Overloads method register, for enabling direct POST requests between RP and OP
+LWP::Protocol::PSGI->register(
+ sub {
+ my $req = Plack::Request->new(@_);
+ ok( $req->uri =~ m#http://auth.((?:op|rp|idp)).com(.*)#, ' REST request' );
+ my $host = $1;
+ my $url = $2;
+ my ( $res, $client );
+ count(1);
+ if ( $host eq 'op' ) {
+ pass(" Request from RP to OP, endpoint $url");
+ $client = $op;
+ }
+ elsif ( $host eq 'rp' ) {
+ pass(' Request from OP to RP');
+ $client = $rp;
+ }
+ elsif ( $host eq 'idp' ) {
+ pass(' Request to IDP');
+ $client = $idp;
+ }
+ else {
+ fail(' Aborting REST request (external)');
+ return HTTP::Response->new(500);
+ }
+ if ( $req->method =~ /^post$/i ) {
+ my $s = $req->content;
+ ok(
+ $res = $client->_post(
+ $url, IO::String->new($s),
+ length => length($s),
+ type => $req->header('Content-Type'),
+ ),
+ ' Execute post request'
+ );
+ }
+ else {
+ ok(
+ $res = $client->_get(
+ $url,
+ custom => {
+ HTTP_AUTHORIZATION => $req->header('Authorization'),
+ }
+ ),
+ ' Execute get request'
+ );
+ }
+ ok( $res->[0] == 200, ' Response is 200' );
+ ok( getHeader( $res, 'Content-Type' ) =~ m#^application/json#,
+ ' Content is JSON' )
+ or explain( $res->[1], 'Content-Type => application/json' );
+ count(4);
+ return $res;
+ }
+);
+
+
+
+SKIP: {
+ eval "use Lasso";
+ if ($@) {
+ skip 'Lasso not found', $maintests;
+ }
+
+ # Initialization
+ $op = register( 'op', \&op );
+
+ ok(
+ $res = $op->_get('/oauth2/jwks'),
+ 'Get JWKS, endpoint /oauth2/jwks'
+ );
+ expectOK($res);
+ my $jwks = $res->[2]->[0];
+
+ ok(
+ $res = $op->_get('/.well-known/openid-configuration'),
+ 'Get metadata, endpoint /.well-known/openid-configuration'
+ );
+ expectOK($res);
+ my $metadata = $res->[2]->[0];
+
+ $idp = register( 'idp', \&idp );
+
+ $rp = register( 'rp', sub { rp( $jwks, $metadata ) } );
+
+
+ # LOGIN PROCESS ############################################################
+
+ # Query RP for auth
+ ok( $res = $rp->_get( '/', accept => 'text/html' ), 'Unauth SP request' );
+ my ( $url, $query ) =
+ expectRedirection( $res,
+ qr#http://auth.op.com(/oauth2/authorize)\?(.*)$# );
+
+ # Push request to OP
+ switch ('op');
+ ok( $res = $op->_get( $url, query => $query, accept => 'text/html' ),
+ "Push request to OP, endpoint $url" );
+
+ my $pdataop = expectCookie( $res, 'lemonldappdata' );
+
+ my ( $urlidp, $queryidp ) =
+ expectRedirection( $res,
+ qr#http://auth.idp.com(/saml/singleSignOn)\?(.*)$# );
+
+ # Push request to IDP
+ switch ('idp');
+
+ # Try to authenticate to IdP
+ ok(
+ $res = $idp->_get( $urlidp, query => $queryidp, accept => 'text/html'),
+ "SAML Authentication on idp, endpoint $urlidp" );
+ my $pdataidp = expectCookie( $res, 'lemonldappdata' );
+
+ my ( $host, $tmp );
+ # expectForm (result, host, uri, @requiredfield)
+ ( $host, $tmp, $query ) = expectForm( $res, '#', undef,
+ ( 'url', 'timezone', 'skin', 'user', 'password' ) );
+ $query =~ s/user=/user=dwho/;
+ $query =~ s/password=/password=dwho/;
+
+ ok(
+ $res = $idp->_post(
+ $urlidp,
+ IO::String->new($query),
+ accept => 'text/html',
+ cookie => "lemonldappdata=$pdataidp",
+ length => length($query),
+ ),
+ "Post authentication, endpoint $urlidp"
+ );
+
+ $pdataidp = expectCookie( $res, 'lemonldappdata' );
+ my $cookieidp = expectCookie( $res, 'lemonldap' );
+
+
+ ( $host, $url, $query ) =
+ expectForm( $res, 'auth.op.com', '/saml/proxySingleSignOnPost',
+ 'SAMLResponse', 'RelayState' );
+
+ my ($resp) = $query =~ qr/SAMLResponse=([^&]*)/;
+
+ # Post SAML response to SP
+ switch ('op');
+ ok(
+ $res = $op->_post(
+ $url, IO::String->new($query),
+ accept => 'text/html',
+ length => length($query),
+ cookie => "lemonldappdata=$pdataop",
+ ),
+ 'Post SAML response to SP'
+ );
+
+ $pdataop = expectCookie( $res, 'lemonldappdata' );
+ my $cookieop = expectCookie( $res, 'lemonldap' );
+
+
+ ( $url, $query ) = expectRedirection( $res, qr#^http://auth.op.com(/oauth2)\?*(.*)$# );
+
+ ok( $res = $op->_get( $url, query => $query,
+ accept => 'text/html',
+ cookie => "lemonldappdata=$pdataop; lemonldap=$cookieop",
+ ),
+ 'Call OP from SAML SP' );
+
+ $pdataop = expectCookie( $res, 'lemonldappdata' );
+
+ # No consent here because we have disabled it (oidcRPMetaDataOptionsBypassConsent)
+
+ ($query) = expectRedirection( $res, qr#^http://auth.rp.com/?\?(.*)$# );
+
+
+ # Push OP response to RP
+ switch ('rp');
+
+ ok( $res = $rp->_get( '/', query => $query, accept => 'text/html' ),
+ 'Call openidconnectcallback on RP' );
+ my $cookierp = expectCookie($res, 'lemonldap');
+
+ # Authentication done on RP + OP + IDP
+
+
+ # LOGOUT PROCESS ###########################################################
+ $url = '/';
+ $query = 'logout=1';
+ ok( $res = $rp->_get( $url, query => $query,
+ accept => 'text/html',
+ cookie => "lemonldap=$cookierp",
+ ),
+ 'Call logout from RP' );
+
+ # lemonldap cookie set to "0"
+ $cookierp = expectCookie( $res, 'lemonldap' );
+ ok( $cookierp eq "0", 'Test empty cookie on RP' );
+
+ # forward logout to OP
+ ( $url, $query ) = expectRedirection( $res, qr#^http://auth.op.com(/.*)\?(.*)$# );
+
+ switch ('op');
+
+ ok( $res = $op->_get( $url, query => $query,
+ accept => 'text/html',
+ cookie => "lemonldappdata=$pdataop; lemonldap=$cookieop",
+ ),
+ 'Forward logout to OP' );
+
+ # expectForm (result, host, uri, @requiredfield)
+ ( $host, $tmp, $query ) = expectForm( $res, '#', undef,
+ ( 'post_logout_redirect_uri', 'confirm', 'skin' ) );
+
+ ok(
+ $res = $op->_post(
+ $url,
+ IO::String->new($query),
+ accept => 'text/html',
+ cookie => "lemonldappdata=$pdataop; lemonldap=$cookieop",
+ length => length($query),
+ ),
+ "Post logout confirmation to OP, endpoint $url"
+ );
+
+ # lemonldap cookie set to "0"
+ $cookieop = expectCookie( $res, 'lemonldap' );
+ ok( $cookieop eq "0", 'Test empty cookie on OP' );
+
+ ( $url, $query ) = expectRedirection( $res, qr#^http://auth.idp.com(/.*)\?(.*)$# );
+
+ switch ('idp');
+
+ ok( $res = $idp->_get( $url, query => $query,
+ accept => 'text/html',
+ cookie => "lemonldappdata=$pdataidp; lemonldap=$cookieidp",
+ ),
+ 'redirect to IdP' );
+
+ # lemonldap cookie set to "0"
+ $cookieidp = expectCookie( $res, 'lemonldap' );
+ ok( $cookieidp eq "0", 'Test empty cookie on IDP' );
+
+ ( $url, $query ) = expectRedirection( $res, qr#^http://auth.op.com(/.*)\?(.*)$# );
+
+ switch ('op');
+
+ ok( $res = $op->_get( $url, query => $query,
+ accept => 'text/html',
+ cookie => "lemonldappdata=$pdataop; lemonldap=$cookieop",
+ ),
+ 'redirect to OP' );
+
+ expectOK($res);
+
+}
+
+count($maintests);
+clean_sessions();
+done_testing( count() );
+
+sub op {
+ return LLNG::Manager::Test->new( {
+ ini => {
+ logLevel => $debug,
+ domain => 'op.com',
+ portal => 'http://auth.op.com',
+ authentication => 'SAML',
+ userDB => 'Same',
+ issuerDBOpenIDConnectActivation => "1",
+ oidcRPMetaDataExportedVars => {
+ rp => {
+ email => "mail",
+ family_name => "cn",
+ name => "cn"
+ }
+ },
+ oidcServiceAllowHybridFlow => 1,
+ oidcServiceAllowImplicitFlow => 1,
+ oidcServiceAllowAuthorizationCodeFlow => 1,
+ oidcRPMetaDataOptions => {
+ rp => {
+ oidcRPMetaDataOptionsDisplayName => "RP",
+ oidcRPMetaDataOptionsIDTokenExpiration => 3600,
+ oidcRPMetaDataOptionsClientID => "rpid",
+ oidcRPMetaDataOptionsIDTokenSignAlg => "HS512",
+ oidcRPMetaDataOptionsBypassConsent => 1,
+ oidcRPMetaDataOptionsClientSecret => "rpsecret",
+ oidcRPMetaDataOptionsUserIDAttr => "",
+ oidcRPMetaDataOptionsAccessTokenExpiration => 3600
+ }
+ },
+ oidcOPMetaDataOptions => {},
+ oidcOPMetaDataJSON => {},
+ oidcOPMetaDataJWKS => {},
+ oidcServiceMetaDataAuthnContext => {
+ 'loa-4' => 4,
+ 'loa-1' => 1,
+ 'loa-5' => 5,
+ 'loa-2' => 2,
+ 'loa-3' => 3
+ },
+ oidcServicePrivateKeySig => oidc_key_op_private_sig,
+ oidcServicePublicKeySig => oidc_key_op_public_sig,
+
+ samlOrganizationDisplayName => "SP",
+ samlOrganizationName => "SP",
+ samlOrganizationURL => "http://www.op.com/",
+ samlServicePrivateKeyEnc => saml_key_sp_private_enc,
+ samlServicePrivateKeySig => saml_key_sp_private_sig,
+ samlServicePublicKeyEnc => saml_key_sp_public_enc,
+ samlServicePublicKeySig => saml_key_sp_public_sig,
+ samlIDPSSODescriptorWantAuthnRequestsSigned => 1,
+ samlSPSSODescriptorWantAssertionsSigned => 1,
+ samlIDPMetaDataXML => {
+ 'idp' => {
+ samlIDPMetaDataXML =>
+ samlIDPMetaDataXML( 'idp', 'HTTP-Redirect' )
+ },
+ },
+ samlIDPMetaDataOptions => {
+ 'idp' => {
+ 'samlIDPMetaDataOptionsAdaptSessionUtime' => 0,
+ 'samlIDPMetaDataOptionsAllowLoginFromIDP' => 0,
+ 'samlIDPMetaDataOptionsAllowProxiedAuthn' => 0,
+ 'samlIDPMetaDataOptionsCheckAudience' => 1,
+ 'samlIDPMetaDataOptionsCheckSLOMessageSignature' => 1,
+ 'samlIDPMetaDataOptionsCheckSSOMessageSignature' => 1,
+ 'samlIDPMetaDataOptionsCheckTime' => 1,
+ 'samlIDPMetaDataOptionsDisplayName' => 'idp',
+ 'samlIDPMetaDataOptionsEncryptionMode' => 'none',
+ 'samlIDPMetaDataOptionsForceAuthn' => 0,
+ 'samlIDPMetaDataOptionsForceUTF8' => 0,
+ 'samlIDPMetaDataOptionsIcon' => '',
+ 'samlIDPMetaDataOptionsIsPassive' => 0,
+ 'samlIDPMetaDataOptionsNameIDFormat' => '',
+ 'samlIDPMetaDataOptionsRelayStateURL' => 0,
+ 'samlIDPMetaDataOptionsRequestedAuthnContext' => '',
+ 'samlIDPMetaDataOptionsResolutionRule' => '',
+ 'samlIDPMetaDataOptionsSLOBinding' => 'http-redirect',
+ 'samlIDPMetaDataOptionsSSOBinding' => 'http-redirect',
+ 'samlIDPMetaDataOptionsSignSLOMessage' => 1,
+ 'samlIDPMetaDataOptionsSignSSOMessage' => 1,
+ 'samlIDPMetaDataOptionsSignatureMethod' => '',
+ 'samlIDPMetaDataOptionsStoreSAMLToken' => 0
+ }
+ },
+ samlIDPMetaDataExportedAttributes => {
+ 'idp' => {
+ 'cn' => '1;cn',
+ 'uid' => '1;uid'
+ }
+ },
+ }
+ }
+ );
+}
+
+sub rp {
+ my ( $jwks, $metadata ) = @_;
+ return LLNG::Manager::Test->new( {
+ ini => {
+ logLevel => $debug,
+ domain => 'rp.com',
+ portal => 'http://auth.rp.com',
+ authentication => 'OpenIDConnect',
+ userDB => 'Same',
+ restSessionServer => 1,
+ oidcOPMetaDataExportedVars => {
+ op => {
+ cn => "name",
+ uid => "sub",
+ sn => "family_name",
+ mail => "email"
+ }
+ },
+ oidcOPMetaDataOptions => {
+ op => {
+ oidcOPMetaDataOptionsJWKSTimeout => 0,
+ oidcOPMetaDataOptionsClientSecret => "rpsecret",
+ oidcOPMetaDataOptionsScope => "openid profile",
+ oidcOPMetaDataOptionsStoreIDToken => 0,
+ oidcOPMetaDataOptionsDisplay => "",
+ oidcOPMetaDataOptionsClientID => "rpid",
+ oidcOPMetaDataOptionsConfigurationURI =>
+ "https://auth.op.com/.well-known/openid-configuration"
+ }
+ },
+ oidcOPMetaDataJWKS => {
+ op => $jwks,
+ },
+ oidcOPMetaDataJSON => {
+ op => $metadata,
+ }
+ }
+ }
+ );
+}
+
+sub idp {
+ return LLNG::Manager::Test->new( {
+ ini => {
+ logLevel => $debug,
+ domain => 'idp.com',
+ portal => 'http://auth.idp.com',
+ authentication => 'Demo',
+ userDB => 'Same',
+ issuerDBSAMLActivation => 1,
+ restSessionServer => 1,
+ samlSPMetaDataOptions => {
+ sp => {
+ 'samlSPMetaDataOptionsCheckSLOMessageSignature' => 1,
+ 'samlSPMetaDataOptionsCheckSSOMessageSignature' => 1,
+ 'samlSPMetaDataOptionsEnableIDPInitiatedURL' => 0,
+ 'samlSPMetaDataOptionsEncryptionMode' => 'none',
+ 'samlSPMetaDataOptionsForceUTF8' => 1,
+ 'samlSPMetaDataOptionsNameIDFormat' => '',
+ 'samlSPMetaDataOptionsNotOnOrAfterTimeout' => 72000,
+ 'samlSPMetaDataOptionsOneTimeUse' => 0,
+ 'samlSPMetaDataOptionsSessionNotOnOrAfterTimeout' => 72000,
+ 'samlSPMetaDataOptionsSignSLOMessage' => -1,
+ 'samlSPMetaDataOptionsSignSSOMessage' => 1,
+ 'samlSPMetaDataOptionsSignatureMethod' => ''
+ }
+ },
+ samlSPMetaDataXML => {
+ sp => {
+ samlSPMetaDataXML =>
+ samlSPMetaDataXML( 'op', 'HTTP-Redirect' ),
+ 'samlSPSSODescriptorAuthnRequestsSigned' => 1,
+ 'samlSPSSODescriptorWantAssertionsSigned' => 1,
+ }
+ },
+ samlSPMetaDataExportedAttributes => {
+ 'sp' => {
+ 'cn' => '1;cn',
+ 'uid' => '1;uid'
+ }
+ },
+ samlOrganizationDisplayName => "IDP",
+ samlOrganizationName => "IDP",
+ samlOrganizationURL => "http://www.idp.com",
+ samlServicePublicKeySig => saml_key_idp_public_sig,
+ samlServicePrivateKeyEnc => saml_key_idp_private_enc,
+ samlServicePrivateKeySig => saml_key_idp_private_sig,
+ samlServicePublicKeyEnc => saml_key_idp_public_enc,
+ samlSPSSODescriptorAuthnRequestsSigned => 1,
+ },
+ }
+ );
+}
diff --git a/lemonldap-ng-portal/t/37-Logout-from-OIDC-RP-to-SAML-IDP-SOAP.t b/lemonldap-ng-portal/t/37-Logout-from-OIDC-RP-to-SAML-IDP-SOAP.t
new file mode 100644
index 000000000..1f2327f2c
--- /dev/null
+++ b/lemonldap-ng-portal/t/37-Logout-from-OIDC-RP-to-SAML-IDP-SOAP.t
@@ -0,0 +1,475 @@
+use lib 'inc';
+use Test::More;
+use strict;
+use IO::String;
+use LWP::UserAgent;
+use LWP::Protocol::PSGI;
+use MIME::Base64;
+
+
+# ------------ --------------------------- ----------------
+# | OIDC RP | <-> | OIDC provider + SAML SP | <-> | SAML IdP |
+# ------------ --------------------------- ----------------
+#
+# Use case:
+# - login from RP up to SAML IdP
+# - logout asked from RP, and propagated up to SAML IdP
+# logout between SAML SP and IdP is done with SOAP binding
+
+BEGIN {
+ require 't/test-lib.pm';
+ require 't/oidc-lib.pm';
+ require 't/saml-lib.pm';
+}
+
+my $maintests = 17;
+my $debug = 'error';
+#my $debug = 'error';
+my ( $op, $rp, $idp, $res );
+
+# Overloads method register, for enabling direct POST requests between RP and OP
+LWP::Protocol::PSGI->register(
+ sub {
+ my $req = Plack::Request->new(@_);
+ ok( $req->uri =~ m#http://auth.((?:op|rp|idp)).com(.*)#, ' REST request' );
+ my $host = $1;
+ my $url = $2;
+ my ( $res, $client );
+ count(1);
+ if ( $host eq 'op' ) {
+ pass(" Request from RP to OP, endpoint $url");
+ $client = $op;
+ }
+ elsif ( $host eq 'rp' ) {
+ pass(' Request from OP to RP');
+ $client = $rp;
+ }
+ elsif ( $host eq 'idp' ) {
+ pass(' Request to IDP');
+ $client = $idp;
+ }
+ else {
+ fail(' Aborting REST request (external)');
+ return HTTP::Response->new(500);
+ }
+ if ( $req->method =~ /^post$/i ) {
+ my $s = $req->content;
+ ok(
+ $res = $client->_post(
+ $url, IO::String->new($s),
+ length => length($s),
+ type => $req->header('Content-Type'),
+ ),
+ ' Execute post request'
+ );
+ }
+ else {
+ ok(
+ $res = $client->_get(
+ $url,
+ custom => {
+ HTTP_AUTHORIZATION => $req->header('Authorization'),
+ }
+ ),
+ ' Execute get request'
+ );
+ }
+ ok( $res->[0] == 200, ' Response is 200' );
+ ok( getHeader( $res, 'Content-Type' ) =~ m#^(application/json|text/xml)#,
+ ' Content is JSON|XML' )
+ or explain( $res->[1], 'Content-Type => (application/json|text/xml)' );
+ count(4);
+ return $res;
+ }
+);
+
+
+
+SKIP: {
+ eval "use Lasso";
+ if ($@) {
+ skip 'Lasso not found', $maintests;
+ }
+
+ # Initialization
+ $op = register( 'op', \&op );
+
+ ok(
+ $res = $op->_get('/oauth2/jwks'),
+ 'Get JWKS, endpoint /oauth2/jwks'
+ );
+ expectOK($res);
+ my $jwks = $res->[2]->[0];
+
+ ok(
+ $res = $op->_get('/.well-known/openid-configuration'),
+ 'Get metadata, endpoint /.well-known/openid-configuration'
+ );
+ expectOK($res);
+ my $metadata = $res->[2]->[0];
+
+ $idp = register( 'idp', \&idp );
+
+ $rp = register( 'rp', sub { rp( $jwks, $metadata ) } );
+
+
+ # LOGIN PROCESS ############################################################
+
+ # Query RP for auth
+ ok( $res = $rp->_get( '/', accept => 'text/html' ), 'Unauth SP request' );
+ my ( $url, $query ) =
+ expectRedirection( $res,
+ qr#http://auth.op.com(/oauth2/authorize)\?(.*)$# );
+
+ # Push request to OP
+ switch ('op');
+ ok( $res = $op->_get( $url, query => $query, accept => 'text/html' ),
+ "Push request to OP, endpoint $url" );
+
+ my $pdataop = expectCookie( $res, 'lemonldappdata' );
+
+ my ( $urlidp, $queryidp ) =
+ expectRedirection( $res,
+ qr#http://auth.idp.com(/saml/singleSignOn)\?(.*)$# );
+
+ # Push request to IDP
+ switch ('idp');
+
+ # Try to authenticate to IdP
+ ok(
+ $res = $idp->_get( $urlidp, query => $queryidp, accept => 'text/html'),
+ "SAML Authentication on idp, endpoint $urlidp" );
+ my $pdataidp = expectCookie( $res, 'lemonldappdata' );
+
+ my ( $host, $tmp );
+ # expectForm (result, host, uri, @requiredfield)
+ ( $host, $tmp, $query ) = expectForm( $res, '#', undef,
+ ( 'url', 'timezone', 'skin', 'user', 'password' ) );
+ $query =~ s/user=/user=dwho/;
+ $query =~ s/password=/password=dwho/;
+
+ ok(
+ $res = $idp->_post(
+ $urlidp,
+ IO::String->new($query),
+ accept => 'text/html',
+ cookie => "lemonldappdata=$pdataidp",
+ length => length($query),
+ ),
+ "Post authentication, endpoint $urlidp"
+ );
+
+ $pdataidp = expectCookie( $res, 'lemonldappdata' );
+ my $cookieidp = expectCookie( $res, 'lemonldap' );
+
+
+ ( $host, $url, $query ) =
+ expectForm( $res, 'auth.op.com', '/saml/proxySingleSignOnPost',
+ 'SAMLResponse', 'RelayState' );
+
+ my ($resp) = $query =~ qr/SAMLResponse=([^&]*)/;
+
+ # Post SAML response to SP
+ switch ('op');
+ ok(
+ $res = $op->_post(
+ $url, IO::String->new($query),
+ accept => 'text/html',
+ length => length($query),
+ cookie => "lemonldappdata=$pdataop",
+ ),
+ 'Post SAML response to SP'
+ );
+
+ $pdataop = expectCookie( $res, 'lemonldappdata' );
+ my $cookieop = expectCookie( $res, 'lemonldap' );
+
+
+ ( $url, $query ) = expectRedirection( $res, qr#^http://auth.op.com(/oauth2)\?*(.*)$# );
+
+ ok( $res = $op->_get( $url, query => $query,
+ accept => 'text/html',
+ cookie => "lemonldappdata=$pdataop; lemonldap=$cookieop",
+ ),
+ 'Call OP from SAML SP' );
+
+ $pdataop = expectCookie( $res, 'lemonldappdata' );
+
+ # No consent here because we have disabled it (oidcRPMetaDataOptionsBypassConsent)
+
+ ($query) = expectRedirection( $res, qr#^http://auth.rp.com/?\?(.*)$# );
+
+
+ # Push OP response to RP
+ switch ('rp');
+
+ ok( $res = $rp->_get( '/', query => $query, accept => 'text/html' ),
+ 'Call openidconnectcallback on RP' );
+ my $cookierp = expectCookie($res, 'lemonldap');
+
+ # Authentication done on RP + OP + IDP
+
+
+ # LOGOUT PROCESS ###########################################################
+ $url = '/';
+ $query = 'logout=1';
+ ok( $res = $rp->_get( $url, query => $query,
+ accept => 'text/html',
+ cookie => "lemonldap=$cookierp",
+ ),
+ 'Call logout from RP' );
+
+ # lemonldap cookie set to "0"
+ $cookierp = expectCookie( $res, 'lemonldap' );
+ ok( $cookierp eq "0", 'Test empty cookie on RP' );
+
+ # forward logout to OP
+ ( $url, $query ) = expectRedirection( $res, qr#^http://auth.op.com(/.*)\?(.*)$# );
+
+ switch ('op');
+
+ ok( $res = $op->_get( $url, query => $query,
+ accept => 'text/html',
+ cookie => "lemonldappdata=$pdataop; lemonldap=$cookieop",
+ ),
+ 'Forward logout to OP' );
+
+ # expectForm (result, host, uri, @requiredfield)
+ ( $host, $tmp, $query ) = expectForm( $res, '#', undef,
+ ( 'post_logout_redirect_uri', 'confirm', 'skin' ) );
+
+ ok(
+ $res = $op->_post(
+ $url,
+ IO::String->new($query),
+ accept => 'text/html',
+ cookie => "lemonldappdata=$pdataop; lemonldap=$cookieop",
+ length => length($query),
+ ),
+ "Post logout confirmation to OP, endpoint $url"
+ );
+
+ # lemonldap cookie set to "0"
+ $cookieop = expectCookie( $res, 'lemonldap' );
+ ok( $cookieop eq "0", 'Test empty cookie on OP' );
+
+ ( $url, $query ) = expectRedirection( $res, qr#^http://auth.rp.com(/?.*)\?(.*)$# );
+
+ switch ('rp');
+
+ ok( $res = $rp->_get( $url, query => $query,
+ accept => 'text/html',
+ cookie => "lemonldap=$cookierp",
+ ),
+ 'redirect to RP' );
+
+ expectOK($res);
+
+ # test connexion on IDP
+ switch('idp');
+ ok( $res = $idp->_get( '/', query => '',
+ accept => 'text/html',
+ cookie => "lemonldap=$cookieidp",
+ ),
+ 'Test if still logged on IDP' );
+
+ like( $res->[2]->[0], qr/userfield/,
+ 'test presence of user field in form (prove successful logout)' );
+
+}
+
+count($maintests);
+clean_sessions();
+done_testing( count() );
+
+sub op {
+ return LLNG::Manager::Test->new( {
+ ini => {
+ logLevel => $debug,
+ domain => 'op.com',
+ portal => 'http://auth.op.com',
+ authentication => 'SAML',
+ userDB => 'Same',
+ issuerDBOpenIDConnectActivation => "1",
+ oidcRPMetaDataExportedVars => {
+ rp => {
+ email => "mail",
+ family_name => "cn",
+ name => "cn"
+ }
+ },
+ oidcServiceAllowHybridFlow => 1,
+ oidcServiceAllowImplicitFlow => 1,
+ oidcServiceAllowAuthorizationCodeFlow => 1,
+ oidcRPMetaDataOptions => {
+ rp => {
+ oidcRPMetaDataOptionsDisplayName => "RP",
+ oidcRPMetaDataOptionsIDTokenExpiration => 3600,
+ oidcRPMetaDataOptionsClientID => "rpid",
+ oidcRPMetaDataOptionsIDTokenSignAlg => "HS512",
+ oidcRPMetaDataOptionsBypassConsent => 1,
+ oidcRPMetaDataOptionsClientSecret => "rpsecret",
+ oidcRPMetaDataOptionsUserIDAttr => "",
+ oidcRPMetaDataOptionsAccessTokenExpiration => 3600,
+ oidcRPMetaDataOptionsPostLogoutRedirectUris => 'http://auth.rp.com?logout=1',
+ }
+ },
+ oidcOPMetaDataOptions => {},
+ oidcOPMetaDataJSON => {},
+ oidcOPMetaDataJWKS => {},
+ oidcServiceMetaDataAuthnContext => {
+ 'loa-4' => 4,
+ 'loa-1' => 1,
+ 'loa-5' => 5,
+ 'loa-2' => 2,
+ 'loa-3' => 3
+ },
+ oidcServicePrivateKeySig => oidc_key_op_private_sig,
+ oidcServicePublicKeySig => oidc_key_op_public_sig,
+
+ samlOrganizationDisplayName => "SP",
+ samlOrganizationName => "SP",
+ samlOrganizationURL => "http://www.op.com/",
+ samlServicePrivateKeyEnc => saml_key_sp_private_enc,
+ samlServicePrivateKeySig => saml_key_sp_private_sig,
+ samlServicePublicKeyEnc => saml_key_sp_public_enc,
+ samlServicePublicKeySig => saml_key_sp_public_sig,
+ samlIDPSSODescriptorWantAuthnRequestsSigned => 1,
+ samlSPSSODescriptorWantAssertionsSigned => 1,
+ samlIDPMetaDataXML => {
+ 'idp' => {
+ samlIDPMetaDataXML =>
+ samlIDPComplexMetaDataXML( 'idp', 'HTTP-Redirect', 'SOAP' )
+ },
+ },
+ samlIDPMetaDataOptions => {
+ 'idp' => {
+ 'samlIDPMetaDataOptionsAdaptSessionUtime' => 0,
+ 'samlIDPMetaDataOptionsAllowLoginFromIDP' => 0,
+ 'samlIDPMetaDataOptionsAllowProxiedAuthn' => 0,
+ 'samlIDPMetaDataOptionsCheckAudience' => 1,
+ 'samlIDPMetaDataOptionsCheckSLOMessageSignature' => 1,
+ 'samlIDPMetaDataOptionsCheckSSOMessageSignature' => 1,
+ 'samlIDPMetaDataOptionsCheckTime' => 1,
+ 'samlIDPMetaDataOptionsDisplayName' => 'idp',
+ 'samlIDPMetaDataOptionsEncryptionMode' => 'none',
+ 'samlIDPMetaDataOptionsForceAuthn' => 0,
+ 'samlIDPMetaDataOptionsForceUTF8' => 0,
+ 'samlIDPMetaDataOptionsIcon' => '',
+ 'samlIDPMetaDataOptionsIsPassive' => 0,
+ 'samlIDPMetaDataOptionsNameIDFormat' => '',
+ 'samlIDPMetaDataOptionsRelayStateURL' => 0,
+ 'samlIDPMetaDataOptionsRequestedAuthnContext' => '',
+ 'samlIDPMetaDataOptionsResolutionRule' => '',
+ 'samlIDPMetaDataOptionsSLOBinding' => 'http-soap',
+ 'samlIDPMetaDataOptionsSSOBinding' => 'http-redirect',
+ 'samlIDPMetaDataOptionsSignSLOMessage' => 1,
+ 'samlIDPMetaDataOptionsSignSSOMessage' => 1,
+ 'samlIDPMetaDataOptionsSignatureMethod' => '',
+ 'samlIDPMetaDataOptionsStoreSAMLToken' => 0
+ }
+ },
+ samlIDPMetaDataExportedAttributes => {
+ 'idp' => {
+ 'cn' => '1;cn',
+ 'uid' => '1;uid'
+ }
+ },
+ }
+ }
+ );
+}
+
+sub rp {
+ my ( $jwks, $metadata ) = @_;
+ return LLNG::Manager::Test->new( {
+ ini => {
+ logLevel => $debug,
+ domain => 'rp.com',
+ portal => 'http://auth.rp.com',
+ authentication => 'OpenIDConnect',
+ userDB => 'Same',
+ restSessionServer => 1,
+ oidcOPMetaDataExportedVars => {
+ op => {
+ cn => "name",
+ uid => "sub",
+ sn => "family_name",
+ mail => "email"
+ }
+ },
+ oidcOPMetaDataOptions => {
+ op => {
+ oidcOPMetaDataOptionsJWKSTimeout => 0,
+ oidcOPMetaDataOptionsClientSecret => "rpsecret",
+ oidcOPMetaDataOptionsScope => "openid profile",
+ oidcOPMetaDataOptionsStoreIDToken => 0,
+ oidcOPMetaDataOptionsDisplay => "",
+ oidcOPMetaDataOptionsClientID => "rpid",
+ oidcOPMetaDataOptionsConfigurationURI =>
+ "https://auth.op.com/.well-known/openid-configuration"
+ }
+ },
+ oidcOPMetaDataJWKS => {
+ op => $jwks,
+ },
+ oidcOPMetaDataJSON => {
+ op => $metadata,
+ }
+ }
+ }
+ );
+}
+
+sub idp {
+ return LLNG::Manager::Test->new( {
+ ini => {
+ logLevel => $debug,
+ domain => 'idp.com',
+ portal => 'http://auth.idp.com',
+ authentication => 'Demo',
+ userDB => 'Same',
+ issuerDBSAMLActivation => 1,
+ restSessionServer => 1,
+ samlSPMetaDataOptions => {
+ sp => {
+ 'samlSPMetaDataOptionsCheckSLOMessageSignature' => 1,
+ 'samlSPMetaDataOptionsCheckSSOMessageSignature' => 1,
+ 'samlSPMetaDataOptionsEnableIDPInitiatedURL' => 0,
+ 'samlSPMetaDataOptionsEncryptionMode' => 'none',
+ 'samlSPMetaDataOptionsForceUTF8' => 1,
+ 'samlSPMetaDataOptionsNameIDFormat' => '',
+ 'samlSPMetaDataOptionsNotOnOrAfterTimeout' => 72000,
+ 'samlSPMetaDataOptionsOneTimeUse' => 0,
+ 'samlSPMetaDataOptionsSessionNotOnOrAfterTimeout' => 72000,
+ 'samlSPMetaDataOptionsSignSLOMessage' => -1,
+ 'samlSPMetaDataOptionsSignSSOMessage' => 1,
+ 'samlSPMetaDataOptionsSignatureMethod' => ''
+ }
+ },
+ samlSPMetaDataXML => {
+ sp => {
+ samlSPMetaDataXML =>
+ samlSPComplexMetaDataXML( 'op', 'HTTP-Redirect', 'SOAP' ),
+ 'samlSPSSODescriptorAuthnRequestsSigned' => 1,
+ 'samlSPSSODescriptorWantAssertionsSigned' => 1,
+ }
+ },
+ samlSPMetaDataExportedAttributes => {
+ 'sp' => {
+ 'cn' => '1;cn',
+ 'uid' => '1;uid'
+ }
+ },
+ samlOrganizationDisplayName => "IDP",
+ samlOrganizationName => "IDP",
+ samlOrganizationURL => "http://www.idp.com",
+ samlServicePublicKeySig => saml_key_idp_public_sig,
+ samlServicePrivateKeyEnc => saml_key_idp_private_enc,
+ samlServicePrivateKeySig => saml_key_idp_private_sig,
+ samlServicePublicKeyEnc => saml_key_idp_public_enc,
+ samlSPSSODescriptorAuthnRequestsSigned => 1,
+ },
+ }
+ );
+}
diff --git a/lemonldap-ng-portal/t/74-2F-Required-Issuer-Timeouts.t b/lemonldap-ng-portal/t/74-2F-Required-Issuer-Timeouts.t
new file mode 100644
index 000000000..4c704d5af
--- /dev/null
+++ b/lemonldap-ng-portal/t/74-2F-Required-Issuer-Timeouts.t
@@ -0,0 +1,163 @@
+use Test::More;
+use strict;
+use IO::String;
+
+require 't/test-lib.pm';
+my $maintests = 19;
+
+SKIP: {
+ eval { require Convert::Base32 };
+ if ($@) {
+ skip 'Convert::Base32 is missing', $maintests;
+ }
+ require Lemonldap::NG::Common::TOTP;
+
+ my $client = LLNG::Manager::Test->new( {
+ ini => {
+ logLevel => 'error',
+ totp2fSelfRegistration => 1,
+ totp2fActivation => 1,
+ sfRequired => 1,
+ sfRegisterTimeout => 600,
+ tokenUseGlobalStorage => 1,
+ issuerDBCASActivation => 1,
+ issuersTimeout => 600,
+ }
+ }
+ );
+ my $res;
+
+ # Try to authenticate
+ # -------------------
+ ok(
+ $res = $client->_get(
+ '/cas/login',
+ query => buildForm( {
+ service => "http://cas.example.com/",
+ }
+ ),
+ accept => 'text/html',
+ length => 23
+ ),
+ 'Auth query'
+ );
+ my $pdata = 'lemonldappdata=' . expectCookie( $res, 'lemonldappdata' );
+
+ # Post login form
+ # ---------------
+ ok(
+ $res = $client->_post(
+ '/',
+ IO::String->new('user=dwho&password=dwho'),
+ cookie => $pdata,
+ length => 23
+ ),
+ 'Auth query'
+ );
+ expectRedirection( $res, qr'http://auth.example.com/+2fregisters/?' );
+ $pdata = 'lemonldappdata=' . expectCookie( $res, 'lemonldappdata' );
+
+ # Follow redirection to TOTP form
+ ok( $res = $client->_get( '/2fregisters', cookie => $pdata ),
+ 'Follow redirection to /2fregisters' );
+ ok( $res->[2]->[0] =~ m#/2fregisters/totp#, 'Found TOTP link' );
+
+ # TOTP form
+ ok(
+ $res = $client->_get(
+ '/2fregisters/totp',
+ cookie => $pdata,
+ accept => 'text/html',
+ ),
+ 'Form registration'
+ );
+ ok( $res->[2]->[0] =~ /totpregistration\.(?:min\.)?js/, 'Found TOTP js' );
+
+ # JS query
+ ok(
+ $res = $client->_post(
+ '/2fregisters/totp/getkey', IO::String->new(''),
+ cookie => $pdata,
+ length => 0,
+ ),
+ 'Get new key'
+ );
+ eval { $res = JSON::from_json( $res->[2]->[0] ) };
+ ok( not($@), 'Content is JSON' )
+ or explain( $res->[2]->[0], 'JSON content' );
+ my ( $key, $token );
+ ok( $key = $res->{secret}, 'Found secret' );
+ ok( $token = $res->{token}, 'Found token' );
+ $key = Convert::Base32::decode_base32($key);
+
+ # Wait for regular form timeout to expire
+ Time::Fake->offset("+5m");
+
+ # Post code
+ my $code;
+ ok( $code = Lemonldap::NG::Common::TOTP::_code( undef, $key, 0, 30, 6 ),
+ 'Code' );
+ ok( $code =~ /^\d{6}$/, 'Code contains 6 digits' );
+ my $s = "code=$code&token=$token";
+ ok(
+ $res = $client->_post(
+ '/2fregisters/totp/verify',
+ IO::String->new($s),
+ length => length($s),
+ cookie => $pdata,
+ ),
+ 'Post code'
+ );
+ $pdata = 'lemonldappdata=' . expectCookie( $res, 'lemonldappdata' );
+ eval { $res = JSON::from_json( $res->[2]->[0] ) };
+ ok( not($@), 'Content is JSON' )
+ or explain( $res->[2]->[0], 'JSON content' );
+ ok( $res->{result} == 1, 'Key is registered' );
+
+ # Try to sign-in
+ ok(
+ $res = $client->_post(
+ '/',
+ IO::String->new('user=dwho&password=dwho'),
+ length => 23,
+ cookie => $pdata,
+ accept => 'text/html',
+ ),
+ 'Auth query'
+ );
+ $pdata = 'lemonldappdata=' . expectCookie( $res, 'lemonldappdata' );
+ my ( $host, $url, $query ) =
+ expectForm( $res, undef, '/totp2fcheck', 'token' );
+ ok( $code = Lemonldap::NG::Common::TOTP::_code( undef, $key, 0, 30, 6 ),
+ 'Code' );
+ $query =~ s/code=/code=$code/;
+ ok(
+ $res = $client->_post(
+ '/totp2fcheck', IO::String->new($query),
+ length => length($query),
+ cookie => $pdata,
+ accept => 'text/html',
+ ),
+ 'Post code'
+ );
+ my $id = expectCookie($res);
+ $pdata = expectCookie( $res, 'lemonldappdata' );
+ expectRedirection( $res, qr'http://auth.example.com//cas' );
+
+ # Follow redirection to TOTP form
+ ok(
+ $res = $client->_get(
+ '//cas',
+ cookie => "lemonldap=$id; lemonldappdata=$pdata",
+ accept => 'text/html',
+ ),
+ 'Follow redirection to issuer'
+ );
+ expectRedirection( $res, qr#^http://cas.example.com/\?(ticket.*)# );
+}
+count($maintests);
+
+clean_sessions();
+
+done_testing( count() );
+
diff --git a/lemonldap-ng-portal/t/saml-lib.pm b/lemonldap-ng-portal/t/saml-lib.pm
index ccc611249..fffc0b1b4 100644
--- a/lemonldap-ng-portal/t/saml-lib.pm
+++ b/lemonldap-ng-portal/t/saml-lib.pm
@@ -437,6 +437,190 @@ EOF
;
}
+sub samlSPComplexMetaDataXML {
+ my ( $name, $typeSSO, $typeSLO ) = @_;
+ my $org = uc($name);
+ return <<"EOF"
+
+
+
+
+
+
+
+
+
+ u4iToYAEmWQxgZDihGVzMMql1elPn37domWcvXeU2E4yt2hh5jkQHiFjgodfOlNeRIw5QJVlUBwr
+ +CQvbaKRFXd7BrOhQIDC0TZPRVB0XHarUtsCuDekN4/2GKSzHsoToKUVPWq9thsuek3xkpsJGZNX
+ 7bglfEc9+QQpYTqN1rkdN1PVU0epNMokFFGho5pLRqLUV5+I/QXAL49jfTjaSxsp4UndTI8/+mGS
+ RSq+nrT2zyQRM/vkj5vR9ZVz67HO/+Wk3Mx6RAwkVcMdgMAqCq8odmbI0yCRZiTL9ybKWRKqWJoK
+ J0p5+Q2fPEBPupQZR09Jt/JPuLVSsGfCxi9Nqw==
+ AQAB
+
+
+
+
+
+
+
+
+
+ sRaod2RZ8hMFBl+VhsnhyPM8l/Fj1obnBxfQIaWuHFIFfXiGe/CYHuZ5QJQLnZxHMJX6LL3Sh+Us
+ og3p0jpijpcg0QgfBSEkfopKTgReYN8DiDIll0rV1XdTni7E85Nd1YyNy3ui/ZD+UShWwqu6jLVL
+ R+QUm+/1LIKYb3OCBTvOlY7xHoP6NSU1+Mr+YzGBUacdO2vnNxe/PQhxIeP1zO0njuqGHkwEpy8r
+ UWRZbbDn31TmKjqlhgtsz5HPhbRaYEExhyepKgBiNz+RyxtYXVhuG8OrWQDoS5gYHSjdw1CTJyix
+ eJwyoqA9RGYguG5nh9zndi3LWAh7Z0lx+tIz+w==
+ AQAB
+
+
+
+
+
+
+
+ urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress
+
+ urn:oasis:names:tc:SAML:1.1:nameid-format:X509SubjectName
+
+ urn:oasis:names:tc:SAML:1.1:nameid-format:WindowsDomainQualifiedName
+
+ urn:oasis:names:tc:SAML:2.0:nameid-format:kerberos
+
+ urn:oasis:names:tc:SAML:2.0:nameid-format:entity
+
+ urn:oasis:names:tc:SAML:2.0:nameid-format:transient
+
+
+
+
+
+
+
+
+
+ u4iToYAEmWQxgZDihGVzMMql1elPn37domWcvXeU2E4yt2hh5jkQHiFjgodfOlNeRIw5QJVlUBwr
+ +CQvbaKRFXd7BrOhQIDC0TZPRVB0XHarUtsCuDekN4/2GKSzHsoToKUVPWq9thsuek3xkpsJGZNX
+ 7bglfEc9+QQpYTqN1rkdN1PVU0epNMokFFGho5pLRqLUV5+I/QXAL49jfTjaSxsp4UndTI8/+mGS
+ RSq+nrT2zyQRM/vkj5vR9ZVz67HO/+Wk3Mx6RAwkVcMdgMAqCq8odmbI0yCRZiTL9ybKWRKqWJoK
+ J0p5+Q2fPEBPupQZR09Jt/JPuLVSsGfCxi9Nqw==
+ AQAB
+
+
+
+
+
+
+
+
+
+ sRaod2RZ8hMFBl+VhsnhyPM8l/Fj1obnBxfQIaWuHFIFfXiGe/CYHuZ5QJQLnZxHMJX6LL3Sh+Us
+ og3p0jpijpcg0QgfBSEkfopKTgReYN8DiDIll0rV1XdTni7E85Nd1YyNy3ui/ZD+UShWwqu6jLVL
+ R+QUm+/1LIKYb3OCBTvOlY7xHoP6NSU1+Mr+YzGBUacdO2vnNxe/PQhxIeP1zO0njuqGHkwEpy8r
+ UWRZbbDn31TmKjqlhgtsz5HPhbRaYEExhyepKgBiNz+RyxtYXVhuG8OrWQDoS5gYHSjdw1CTJyix
+ eJwyoqA9RGYguG5nh9zndi3LWAh7Z0lx+tIz+w==
+ AQAB
+
+
+
+
+
+
+
+ urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress
+
+ urn:oasis:names:tc:SAML:1.1:nameid-format:X509SubjectName
+
+ urn:oasis:names:tc:SAML:1.1:nameid-format:WindowsDomainQualifiedName
+
+ urn:oasis:names:tc:SAML:2.0:nameid-format:kerberos
+
+ urn:oasis:names:tc:SAML:2.0:nameid-format:entity
+
+ urn:oasis:names:tc:SAML:2.0:nameid-format:transient
+
+
+
+
+
+
+
+
+
+
+
+ u4iToYAEmWQxgZDihGVzMMql1elPn37domWcvXeU2E4yt2hh5jkQHiFjgodfOlNeRIw5QJVlUBwr
+ +CQvbaKRFXd7BrOhQIDC0TZPRVB0XHarUtsCuDekN4/2GKSzHsoToKUVPWq9thsuek3xkpsJGZNX
+ 7bglfEc9+QQpYTqN1rkdN1PVU0epNMokFFGho5pLRqLUV5+I/QXAL49jfTjaSxsp4UndTI8/+mGS
+ RSq+nrT2zyQRM/vkj5vR9ZVz67HO/+Wk3Mx6RAwkVcMdgMAqCq8odmbI0yCRZiTL9ybKWRKqWJoK
+ J0p5+Q2fPEBPupQZR09Jt/JPuLVSsGfCxi9Nqw==
+ AQAB
+
+
+
+
+
+
+
+
+
+ sRaod2RZ8hMFBl+VhsnhyPM8l/Fj1obnBxfQIaWuHFIFfXiGe/CYHuZ5QJQLnZxHMJX6LL3Sh+Us
+ og3p0jpijpcg0QgfBSEkfopKTgReYN8DiDIll0rV1XdTni7E85Nd1YyNy3ui/ZD+UShWwqu6jLVL
+ R+QUm+/1LIKYb3OCBTvOlY7xHoP6NSU1+Mr+YzGBUacdO2vnNxe/PQhxIeP1zO0njuqGHkwEpy8r
+ UWRZbbDn31TmKjqlhgtsz5HPhbRaYEExhyepKgBiNz+RyxtYXVhuG8OrWQDoS5gYHSjdw1CTJyix
+ eJwyoqA9RGYguG5nh9zndi3LWAh7Z0lx+tIz+w==
+ AQAB
+
+
+
+
+
+
+ urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress
+
+ urn:oasis:names:tc:SAML:1.1:nameid-format:X509SubjectName
+
+ urn:oasis:names:tc:SAML:1.1:nameid-format:WindowsDomainQualifiedName
+
+ urn:oasis:names:tc:SAML:2.0:nameid-format:kerberos
+
+ urn:oasis:names:tc:SAML:2.0:nameid-format:entity
+
+ urn:oasis:names:tc:SAML:2.0:nameid-format:transient
+
+
+ $org
+
+ $org
+
+ http://www.$name.com
+
+
+EOF
+ ;
+}
+
sub samlProxyMetaDataXML {
my ( $name, $type ) = @_;
my $org = uc($name);
@@ -623,6 +807,192 @@ EOF
;
}
+sub samlProxyComplexMetaDataXML {
+ my ( $name, $typeSSO, $typeSLO ) = @_;
+ my $org = uc($name);
+ return <<"EOF"
+
+
+
+
+
+
+
+ ztmb1JZk/agkYYm23D4dqaLS4EKHKrjO4eBvwtWZLexAGR1KDpcrHqLyqJoal+q4A8drI7lxElSt
+6xRKJ4DIxQM1jqRcmE6EzdL6BfTaRace3zIuhjDSQUZJdtFtlJynQT1cJbx5ZYhqZbYANm9NZRcY
+Z5gWeyF9nl41xA79AMuYlpt7eWDR8cnQJXwV790991FQ9yA2BBgTdSKkFqZ72P4lWu4shz3JCGf5
+hyq03hCHQ7bsfpgAdCrbQPTuJNFtS599ClMu+AcRcwJcS233pHd306PRHCXn3Eapq6gEoHxgLVNp
++luAIhRA9EaOnZ0nVkFwFKn3vLXzV01iTliMeQ==
+
+ AQAB
+
+
+
+
+
+
+
+
+
+ 2vzoUiQ4GsM5qLjoxslEDKj+RrPh/A743JCWe1Hbadjd5yD4gPwmJUxMF+MJcQlo/TkmKbTonPdI
+oAqDknbUxfFTntp0VkdKrB64xr0Stpy7123hPszat3SbU3RYypdobEcuSAS77w9X1KnkRL1+CIe5
+9qSsghO3l3b2IJ6qPFXdx/cro7+K3O7w8wAEJ9KmxA0KdiZpSFgTAqfNDSKx8NLwZOeDpsHouAxy
+1E2kine+9ESBTRAM2PgiGZvU5JA1SZscdEg3wTftJxxPFnAJMwtqM3IVC6B+TqsIP5Wlk1PQQqH7
+5gjtBYDVduynBwU+l/UUmp1aDRZupuH8PF51pw==
+
+ AQAB
+
+
+
+
+
+
+
+
+ urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress
+
+ urn:oasis:names:tc:SAML:1.1:nameid-format:X509SubjectName
+
+ urn:oasis:names:tc:SAML:1.1:nameid-format:WindowsDomainQualifiedName
+
+ urn:oasis:names:tc:SAML:2.0:nameid-format:kerberos
+
+ urn:oasis:names:tc:SAML:2.0:nameid-format:entity
+
+ urn:oasis:names:tc:SAML:2.0:nameid-format:transient
+
+
+
+
+
+
+
+
+ ztmb1JZk/agkYYm23D4dqaLS4EKHKrjO4eBvwtWZLexAGR1KDpcrHqLyqJoal+q4A8drI7lxElSt
+6xRKJ4DIxQM1jqRcmE6EzdL6BfTaRace3zIuhjDSQUZJdtFtlJynQT1cJbx5ZYhqZbYANm9NZRcY
+Z5gWeyF9nl41xA79AMuYlpt7eWDR8cnQJXwV790991FQ9yA2BBgTdSKkFqZ72P4lWu4shz3JCGf5
+hyq03hCHQ7bsfpgAdCrbQPTuJNFtS599ClMu+AcRcwJcS233pHd306PRHCXn3Eapq6gEoHxgLVNp
++luAIhRA9EaOnZ0nVkFwFKn3vLXzV01iTliMeQ==
+
+ AQAB
+
+
+
+
+
+
+
+
+
+ 2vzoUiQ4GsM5qLjoxslEDKj+RrPh/A743JCWe1Hbadjd5yD4gPwmJUxMF+MJcQlo/TkmKbTonPdI
+oAqDknbUxfFTntp0VkdKrB64xr0Stpy7123hPszat3SbU3RYypdobEcuSAS77w9X1KnkRL1+CIe5
+9qSsghO3l3b2IJ6qPFXdx/cro7+K3O7w8wAEJ9KmxA0KdiZpSFgTAqfNDSKx8NLwZOeDpsHouAxy
+1E2kine+9ESBTRAM2PgiGZvU5JA1SZscdEg3wTftJxxPFnAJMwtqM3IVC6B+TqsIP5Wlk1PQQqH7
+5gjtBYDVduynBwU+l/UUmp1aDRZupuH8PF51pw==
+
+ AQAB
+
+
+
+
+
+
+
+
+ urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress
+
+ urn:oasis:names:tc:SAML:1.1:nameid-format:X509SubjectName
+
+ urn:oasis:names:tc:SAML:1.1:nameid-format:WindowsDomainQualifiedName
+
+ urn:oasis:names:tc:SAML:2.0:nameid-format:kerberos
+
+ urn:oasis:names:tc:SAML:2.0:nameid-format:entity
+
+ urn:oasis:names:tc:SAML:2.0:nameid-format:transient
+
+
+
+
+
+
+
+
+
+ ztmb1JZk/agkYYm23D4dqaLS4EKHKrjO4eBvwtWZLexAGR1KDpcrHqLyqJoal+q4A8drI7lxElSt
+6xRKJ4DIxQM1jqRcmE6EzdL6BfTaRace3zIuhjDSQUZJdtFtlJynQT1cJbx5ZYhqZbYANm9NZRcY
+Z5gWeyF9nl41xA79AMuYlpt7eWDR8cnQJXwV790991FQ9yA2BBgTdSKkFqZ72P4lWu4shz3JCGf5
+hyq03hCHQ7bsfpgAdCrbQPTuJNFtS599ClMu+AcRcwJcS233pHd306PRHCXn3Eapq6gEoHxgLVNp
++luAIhRA9EaOnZ0nVkFwFKn3vLXzV01iTliMeQ==
+
+ AQAB
+
+
+
+
+
+
+
+
+
+ 2vzoUiQ4GsM5qLjoxslEDKj+RrPh/A743JCWe1Hbadjd5yD4gPwmJUxMF+MJcQlo/TkmKbTonPdI
+oAqDknbUxfFTntp0VkdKrB64xr0Stpy7123hPszat3SbU3RYypdobEcuSAS77w9X1KnkRL1+CIe5
+9qSsghO3l3b2IJ6qPFXdx/cro7+K3O7w8wAEJ9KmxA0KdiZpSFgTAqfNDSKx8NLwZOeDpsHouAxy
+1E2kine+9ESBTRAM2PgiGZvU5JA1SZscdEg3wTftJxxPFnAJMwtqM3IVC6B+TqsIP5Wlk1PQQqH7
+5gjtBYDVduynBwU+l/UUmp1aDRZupuH8PF51pw==
+
+ AQAB
+
+
+
+
+
+
+
+ urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress
+
+ urn:oasis:names:tc:SAML:1.1:nameid-format:X509SubjectName
+
+ urn:oasis:names:tc:SAML:1.1:nameid-format:WindowsDomainQualifiedName
+
+ urn:oasis:names:tc:SAML:2.0:nameid-format:kerberos
+
+ urn:oasis:names:tc:SAML:2.0:nameid-format:entity
+
+ urn:oasis:names:tc:SAML:2.0:nameid-format:transient
+
+
+ $org
+
+ $org
+
+ http://www.$name.com
+
+
+EOF
+ ;
+}
+
sub samlIDPMetaDataXML {
my ( $name, $type ) = @_;
my $org = uc($name);
@@ -804,6 +1174,188 @@ EOF
;
}
+sub samlIDPComplexMetaDataXML {
+ my ( $name, $typeSSO, $typeSLO ) = @_;
+ my $org = uc($name);
+ return <<"EOF"
+
+
+
+
+
+
+
+
+
+ tR/wgDqWB4Maho5V6TjcL/NbNfjgIh7GcgkrB5RZcVT1GTejJlMjUQdgBKBuZXQN+7/29P6UcGq1
+ kYalURq6S8SpeJ1ofp5rBEoD/TIkvU0JOcid65wp+fdzXGXsfiZvHraU74jSCgjP/wqfVGRyBIQz
+ B0SIxSpnrsigqNsE1E94toDMx4wovjHu/9ABAImREV7Sz83OeFF00/sghrjTEJOD/gHf04JCn9Mg
+ NOqvSTysr9LXWg/oUKQDEYeTq9ux6pq/oqv1MxwONbSZPtN5yD41mi+hT8Rh+W8Je8rsiML4VMxz
+ sb1l9303asw6suo5bLTISKNSbu1nt1NkpNxzyw==
+ AQAB
+
+
+
+
+
+
+
+
+
+ nfKBDG/K0TnGT7Xu8q1N45sNWvIK91SqNg8nvN2uVeKoHADTcsus5Xn3id5+8Q9TuMFsW9kIEeXi
+ aPKXQa9ryfSNDhWDWloNkpGEeWif2BnHUu46Abu1UBWb0mH6VwcG1PR4qHruLis1odjQ1qnVDNfS
+ EASVIppEBYjDX203ypmURIzU6h53GRRRlf1BLWkbVn9ysmDeR57Xw5Rsx/+tBlcnMrkv/40DSUke
+ hQIl2JmlFrl2Caik+gU4pd20apA/pNLjBZF0OmGoS08AIR5NMd0KFa6CwZUUSHJqH5GFy5Y2yl4l
+ g8K0klAS9q7L7aXI+eFQZhkwidjpxXnHPyxIGQ==
+ AQAB
+
+
+
+
+
+
+
+ urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress
+
+ urn:oasis:names:tc:SAML:1.1:nameid-format:X509SubjectName
+
+ urn:oasis:names:tc:SAML:1.1:nameid-format:WindowsDomainQualifiedName
+
+ urn:oasis:names:tc:SAML:2.0:nameid-format:kerberos
+
+ urn:oasis:names:tc:SAML:2.0:nameid-format:entity
+
+ urn:oasis:names:tc:SAML:2.0:nameid-format:transient
+
+
+
+
+
+
+
+
+
+ tR/wgDqWB4Maho5V6TjcL/NbNfjgIh7GcgkrB5RZcVT1GTejJlMjUQdgBKBuZXQN+7/29P6UcGq1
+ kYalURq6S8SpeJ1ofp5rBEoD/TIkvU0JOcid65wp+fdzXGXsfiZvHraU74jSCgjP/wqfVGRyBIQz
+ B0SIxSpnrsigqNsE1E94toDMx4wovjHu/9ABAImREV7Sz83OeFF00/sghrjTEJOD/gHf04JCn9Mg
+ NOqvSTysr9LXWg/oUKQDEYeTq9ux6pq/oqv1MxwONbSZPtN5yD41mi+hT8Rh+W8Je8rsiML4VMxz
+ sb1l9303asw6suo5bLTISKNSbu1nt1NkpNxzyw==
+ AQAB
+
+
+
+
+
+
+
+
+
+ nfKBDG/K0TnGT7Xu8q1N45sNWvIK91SqNg8nvN2uVeKoHADTcsus5Xn3id5+8Q9TuMFsW9kIEeXi
+ aPKXQa9ryfSNDhWDWloNkpGEeWif2BnHUu46Abu1UBWb0mH6VwcG1PR4qHruLis1odjQ1qnVDNfS
+ EASVIppEBYjDX203ypmURIzU6h53GRRRlf1BLWkbVn9ysmDeR57Xw5Rsx/+tBlcnMrkv/40DSUke
+ hQIl2JmlFrl2Caik+gU4pd20apA/pNLjBZF0OmGoS08AIR5NMd0KFa6CwZUUSHJqH5GFy5Y2yl4l
+ g8K0klAS9q7L7aXI+eFQZhkwidjpxXnHPyxIGQ==
+ AQAB
+
+
+
+
+
+
+
+ urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress
+
+ urn:oasis:names:tc:SAML:1.1:nameid-format:X509SubjectName
+
+ urn:oasis:names:tc:SAML:1.1:nameid-format:WindowsDomainQualifiedName
+
+ urn:oasis:names:tc:SAML:2.0:nameid-format:kerberos
+
+ urn:oasis:names:tc:SAML:2.0:nameid-format:entity
+
+ urn:oasis:names:tc:SAML:2.0:nameid-format:transient
+
+
+
+
+
+
+
+
+
+
+ tR/wgDqWB4Maho5V6TjcL/NbNfjgIh7GcgkrB5RZcVT1GTejJlMjUQdgBKBuZXQN+7/29P6UcGq1
+ kYalURq6S8SpeJ1ofp5rBEoD/TIkvU0JOcid65wp+fdzXGXsfiZvHraU74jSCgjP/wqfVGRyBIQz
+ B0SIxSpnrsigqNsE1E94toDMx4wovjHu/9ABAImREV7Sz83OeFF00/sghrjTEJOD/gHf04JCn9Mg
+ NOqvSTysr9LXWg/oUKQDEYeTq9ux6pq/oqv1MxwONbSZPtN5yD41mi+hT8Rh+W8Je8rsiML4VMxz
+ sb1l9303asw6suo5bLTISKNSbu1nt1NkpNxzyw==
+ AQAB
+
+
+
+
+
+
+
+
+
+ nfKBDG/K0TnGT7Xu8q1N45sNWvIK91SqNg8nvN2uVeKoHADTcsus5Xn3id5+8Q9TuMFsW9kIEeXi
+ aPKXQa9ryfSNDhWDWloNkpGEeWif2BnHUu46Abu1UBWb0mH6VwcG1PR4qHruLis1odjQ1qnVDNfS
+ EASVIppEBYjDX203ypmURIzU6h53GRRRlf1BLWkbVn9ysmDeR57Xw5Rsx/+tBlcnMrkv/40DSUke
+ hQIl2JmlFrl2Caik+gU4pd20apA/pNLjBZF0OmGoS08AIR5NMd0KFa6CwZUUSHJqH5GFy5Y2yl4l
+ g8K0klAS9q7L7aXI+eFQZhkwidjpxXnHPyxIGQ==
+ AQAB
+
+
+
+
+
+
+ urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress
+
+ urn:oasis:names:tc:SAML:1.1:nameid-format:X509SubjectName
+
+ urn:oasis:names:tc:SAML:1.1:nameid-format:WindowsDomainQualifiedName
+
+ urn:oasis:names:tc:SAML:2.0:nameid-format:kerberos
+
+ urn:oasis:names:tc:SAML:2.0:nameid-format:entity
+
+ urn:oasis:names:tc:SAML:2.0:nameid-format:transient
+
+
+ $org
+
+ $org
+
+ http://www.$name.fr/
+
+
+EOF
+ ;
+}
+
+
=head4 expectXPath($xml_string, $xpath, $namespaces, $value, $message)
Match a XPath expression against the provided string, and verify that the correct value is