portalConsts
PE_OK
PE_REDIRECT
PE_SESSIONEXPIRED
PE_SAML_ART_ERROR
PE_SAML_DESTINATION_ERROR
PE_SAML_SESSION_ERROR
PE_SAML_SIGNATURE_ERROR
PE_SLO_ERROR
PE_SAML_SSO_ERROR
PE_ISSUERMISSINGREQATTR
PE_UNKNOWNPARTNER
PE_SAML_SERVICE_NOT_ALLOWED
PE_UNAUTHORIZEDPARTNER
)
;
our
$VERSION
=
'2.19.0'
;
'Lemonldap::NG::Portal::Lib::SAML'
;
has
rule
=> (
is
=>
'rw'
);
has
ssoUrlRe
=> (
is
=>
'rw'
);
has
ssoUrlArtifact
=> (
is
=>
'rw'
);
has
ssoGetUrl
=> (
is
=>
'rw'
);
use
constant
lsDump
=>
'_lassoSessionDumpI'
;
use
constant
liDump
=>
'_lassoIdentityDumpI'
;
sub
init {
my
(
$self
) =
@_
;
my
$hd
=
$self
->p->HANDLER;
$self
->logger->debug(
"SAML rule -> "
.
$self
->conf->{issuerDBSAMLRule} );
my
$rule
=
$hd
->buildSub(
$hd
->substitute(
$self
->conf->{issuerDBSAMLRule} ) );
unless
(
$rule
) {
my
$error
=
$hd
->tsv->{jail}->error ||
'???'
;
$self
->error(
"Bad SAML activation rule -> $error"
);
return
0;
}
$self
->{rule} =
$rule
;
my
$saml_sso_get_url
=
$self
->ssoGetUrl(
$self
->getMetaDataURL(
"samlIDPSSODescriptorSingleSignOnServiceHTTPRedirect"
, 1
)
);
my
$saml_sso_get_url_ret
=
$self
->getMetaDataURL(
"samlIDPSSODescriptorSingleSignOnServiceHTTPRedirect"
, 2 );
my
$saml_sso_post_url
=
$self
->getMetaDataURL(
"samlIDPSSODescriptorSingleSignOnServiceHTTPPost"
,
1 );
my
$saml_sso_post_url_ret
=
$self
->getMetaDataURL(
"samlIDPSSODescriptorSingleSignOnServiceHTTPPost"
,
2 );
my
$saml_sso_art_url
=
$self
->getMetaDataURL(
"samlIDPSSODescriptorSingleSignOnServiceHTTPArtifact"
, 1 );
my
$saml_sso_art_url_ret
=
$self
->getMetaDataURL(
"samlIDPSSODescriptorSingleSignOnServiceHTTPArtifact"
, 2 );
$self
->ssoUrlRe(
qr/^($saml_sso_get_url|$saml_sso_get_url_ret|$saml_sso_post_url|$saml_sso_post_url_ret|$saml_sso_art_url|$saml_sso_art_url_ret)(?:\?.*)?$/
i
);
$self
->ssoUrlArtifact(
qr/^($saml_sso_art_url|$saml_sso_art_url_ret)(?:\?.*)?$/
i);
my
$res
= (
$self
->Lemonldap::NG::Portal::Main::Issuer::init()
and
$self
->Lemonldap::NG::Portal::Lib::SAML::init()
and
$self
->loadSPs()
and
$self
->loadIDPs()
);
return
0
unless
(
$res
);
if
(
$self
->conf->{samlOverrideIDPEntityID} ) {
$self
->lassoServer->ProviderID(
$self
->conf->{samlOverrideIDPEntityID} );
}
$self
->addUnauthRouteFromMetaDataURL(
"samlIDPSSODescriptorSingleLogoutServiceSOAP"
,
1,
'sloServer'
, [
'POST'
] );
$self
->addUnauthRouteFromMetaDataURL(
"samlIDPSSODescriptorSingleLogoutServiceSOAP"
,
2,
'sloServer'
, [
'POST'
] );
$self
->addUnauthRouteFromMetaDataURL(
"samlIDPSSODescriptorSingleLogoutServiceHTTPRedirect"
,
1,
'sloServer'
, [
'GET'
] );
$self
->addUnauthRouteFromMetaDataURL(
"samlIDPSSODescriptorSingleLogoutServiceHTTPRedirect"
,
2,
'sloServer'
, [
'GET'
] );
$self
->addUnauthRouteFromMetaDataURL(
"samlIDPSSODescriptorSingleLogoutServiceHTTPPost"
,
1,
'sloServer'
, [
'POST'
] );
$self
->addUnauthRouteFromMetaDataURL(
"samlIDPSSODescriptorSingleLogoutServiceHTTPPost"
,
2,
'sloServer'
, [
'POST'
] );
$self
->addAuthRouteFromMetaDataURL(
"samlIDPSSODescriptorSingleLogoutServiceSOAP"
,
1,
'authSloServer'
, [
'POST'
] );
$self
->addAuthRouteFromMetaDataURL(
"samlIDPSSODescriptorSingleLogoutServiceSOAP"
,
2,
'authSloServer'
, [
'POST'
] );
$self
->addAuthRouteFromMetaDataURL(
"samlIDPSSODescriptorSingleLogoutServiceHTTPRedirect"
,
1,
'authSloServer'
, [
'GET'
] );
$self
->addAuthRouteFromMetaDataURL(
"samlIDPSSODescriptorSingleLogoutServiceHTTPRedirect"
,
2,
'authSloServer'
, [
'GET'
] );
$self
->addAuthRouteFromMetaDataURL(
"samlIDPSSODescriptorSingleLogoutServiceHTTPPost"
,
1,
'authSloServer'
, [
'POST'
] );
$self
->addAuthRouteFromMetaDataURL(
"samlIDPSSODescriptorSingleLogoutServiceHTTPPost"
,
2,
'authSloServer'
, [
'POST'
] );
$self
->addRouteFromMetaDataURL(
'samlIDPSSODescriptorArtifactResolutionServiceArtifact'
,
3,
'artifactServer'
, [
'POST'
] );
$self
->addRouteFromMetaDataURL(
'samlAttributeAuthorityDescriptorAttributeServiceSOAP'
,
1,
'attributeServer'
, [
'POST'
] );
$self
->addUnauthRoute(
$self
->
path
=> {
relaySingleLogoutSOAP
=>
'sloRelaySoap'
},
[
'GET'
,
'POST'
]
);
$self
->addAuthRoute(
$self
->
path
=> {
relaySingleLogoutPOST
=>
'sloRelayPost'
},
[
'GET'
,
'POST'
]
);
$self
->addUnauthRoute(
$self
->
path
=> {
relaySingleLogoutPOST
=>
'sloRelayPost'
},
[
'GET'
,
'POST'
]
);
$self
->addUnauthRoute(
$self
->
path
=> {
singleLogoutResume
=>
'sloResume'
},
[
'GET'
,
'POST'
]
);
$self
->addUnauthRoute(
$self
->
path
=> {
relaySingleLogoutTermination
=>
'sloRelayTerm'
},
[
'GET'
,
'POST'
]
);
return
$res
;
}
sub
storeEnv {
my
(
$self
,
$req
) =
@_
;
return
PE_OK
if
(
$req
->uri !~
$self
->ssoUrlRe or
$req
->uri =~
$self
->ssoUrlArtifact );
my
(
$request
,
$response
,
$method
,
$relaystate
,
$artifact
) =
$self
->checkMessage(
$req
,
$req
->uri,
$req
->method,
$req
->content_type );
return
PE_OK
if
(
$artifact
or !
$request
);
my
$login
=
$self
->createLogin(
$self
->lassoServer );
$self
->disableSignatureVerification(
$login
);
$self
->processAuthnRequestMsgWithError(
$login
,
$request
,
'debug'
);
if
(
my
$sp
=
$login
->remote_providerID() ) {
$req
->env->{llng_saml_sp} =
$sp
;
if
(
my
$spConfKey
=
$self
->spList->{
$sp
}->{confKey} ) {
$req
->env->{llng_saml_spconfkey} =
$spConfKey
;
my
$targetAuthnLevel
=
$self
->spOptions->{
$sp
}->{samlSPMetaDataOptionsAuthnLevel};
$req
->pdata->{targetAuthnLevel} =
$targetAuthnLevel
if
$targetAuthnLevel
;
}
if
(
$login
->request ) {
my
$acs
=
$login
->request->AssertionConsumerServiceURL;
$req
->env->{llng_saml_acs} =
$acs
if
$acs
;
}
}
return
PE_OK;
}
sub
ssoMatch {
my
(
$self
,
$req
) =
@_
;
my
$url
=
$self
->normalize_url(
$req
->uri,
$self
->conf->{issuerDBSAMLPath},
$self
->ssoGetUrl );
return
(
$url
=~
$self
->ssoUrlRe or
$req
->data->{_proxiedRequest}
? 1
: 0
);
}
sub
run {
my
(
$self
,
$req
) =
@_
;
my
$login
;
my
$protocolProfile
;
my
$artifact_method
;
my
$authn_context
;
unless
(
$self
->rule->(
$req
,
$req
->sessionInfo ) ) {
$self
->userLogger->error(
'SAML service not authorized'
);
return
PE_SAML_SERVICE_NOT_ALLOWED;
}
my
$session_id
=
$req
->{sessionInfo}->{_session_id} ||
$req
->id;
my
$time
=
$req
->{sessionInfo}->{_utime} ||
time
();
my
$url
=
$req
->uri;
my
$request_method
=
$req
->param(
'issuerMethod'
) ||
$req
->method;
my
$content_type
=
$req
->content_type();
my
$idp_initiated
=
$req
->param(
'IDPInitiated'
);
my
$idp_initiated_sp
=
$req
->param(
'sp'
);
my
$idp_initiated_spConfKey
=
$req
->param(
'spConfKey'
);
my
$idp_initiated_spDest
=
$req
->param(
'spDest'
);
$url
=
$self
->normalize_url(
$url
,
$self
->conf->{issuerDBSAMLPath},
$self
->ssoGetUrl );
my
$domain
=
$req
->param(
'domain'
);
if
(
$domain
) {
$self
->logger->debug(
"Found domain $domain in SAML GET parameter"
);
}
if
(
$url
=~
$self
->ssoUrlRe or
$req
->data->{_proxiedRequest} ) {
$self
->logger->debug(
"URL $url detected as an SSO request URL"
);
my
(
$request
,
$response
,
$method
,
$relaystate
,
$artifact
) =
$self
->checkMessage(
$req
,
$url
,
$request_method
,
$content_type
);
my
$login
=
$self
->createLogin(
$self
->lassoServer );
$self
->disableSignatureVerification(
$login
);
if
(
$request
) {
$req
->data->{_proxiedSamlRequest} =
$login
->request();
$req
->data->{_proxiedRequest} =
$request
;
$req
->data->{_proxiedMethod} =
$method
;
$req
->data->{_proxiedRelayState} =
$relaystate
,
$req
->data->{_proxiedArtifact} =
$artifact
;
}
if
(
$request
or
$idp_initiated
) {
my
$session
=
$req
->{sessionInfo}->{
$self
->lsDump };
my
$identity
=
$req
->{sessionInfo}->{
$self
->liDump };
if
(
$session
) {
unless
(
$self
->setSessionFromDump(
$login
,
$session
) ) {
return
$self
->_failAuthnRequest(
$req
,
msg
=>
"Unable to load Lasso Session"
);
}
$self
->logger->debug(
"Lasso Session loaded"
);
}
if
(
$identity
) {
unless
(
$self
->setIdentityFromDump(
$login
,
$identity
) ) {
return
$self
->_failAuthnRequest(
$req
,
msg
=>
"Unable to load Lasso Identity"
);
}
$self
->logger->debug(
"Lasso Identity loaded"
);
}
my
$result
;
if
(
$idp_initiated
) {
unless
(
$idp_initiated_sp
or
$idp_initiated_spConfKey
) {
return
$self
->_failAuthnRequest(
$req
,
msg
=> (
"sp or spConfKey parameter needed"
.
" to make IDP initiated SSO"
),
userLogger
=> 1,
);
}
unless
(
$idp_initiated_sp
) {
foreach
(
keys
%{
$self
->spList } ) {
if
(
$self
->spList->{
$_
}->{confKey} eq
$idp_initiated_spConfKey
)
{
$idp_initiated_sp
=
$_
;
last
;
}
}
}
else
{
$self
->lazy_load_entityid(
$idp_initiated_sp
);
unless
(
defined
$self
->spList->{
$idp_initiated_sp
} ) {
return
$self
->_failAuthnRequest(
$req
,
msg
=>
"SP $idp_initiated_sp not known"
,
res
=> PE_UNKNOWNPARTNER,
userLogger
=> 1,
);
}
$idp_initiated_spConfKey
=
$self
->spList->{
$idp_initiated_sp
}->{confKey};
}
unless
(
$self
->spOptions->{
$idp_initiated_sp
}
->{samlSPMetaDataOptionsEnableIDPInitiatedURL} )
{
return
$self
->_failAuthnRequest(
$req
,
msg
=> (
"IDP Initiated SSO not allowed"
.
" for SP $idp_initiated_spConfKey"
),
userLogger
=> 1,
);
}
$result
=
$self
->initIdpInitiatedAuthnRequest(
$login
,
$idp_initiated_sp
);
unless
(
$result
) {
return
$self
->_failAuthnRequest(
$req
,
msg
=> (
"SSO: Fail to init IDP Initiated"
.
" authentication request"
)
);
}
my
$nameIDFormatKey
=
$self
->spOptions->{
$idp_initiated_sp
}
->{samlSPMetaDataOptionsNameIDFormat} ||
"email"
;
eval
{
$login
->request()->NameIDPolicy()
->Format(
$self
->getNameIDFormat(
$nameIDFormatKey
) );
};
eval
{
$login
->request()->NameIDPolicy()->AllowCreate(1); };
if
(
$idp_initiated_spDest
) {
$login
->request->AssertionConsumerServiceURL(
$idp_initiated_spDest
);
}
}
my
$lasso_error
;
if
(
$artifact
) {
$result
=
$self
->processArtResponseMsg(
$login
,
$request
);
}
else
{
(
$result
,
$lasso_error
) =
$self
->processAuthnRequestMsgWithError(
$login
,
$request
);
}
unless
(
$result
) {
if
(
$lasso_error
==
Lasso::Constants::SERVER_ERROR_PROVIDER_NOT_FOUND )
{
my
$sp
=
eval
{
$login
->remote_providerID() };
return
$self
->_failAuthnRequest(
$req
,
msg
=> (
"SSO: Incoming entity ID $sp was not found"
.
" in registered Service Providers metadata."
),
res
=> PE_UNKNOWNPARTNER,
);
}
elsif
(
$lasso_error
==
Lasso::Constants::PROFILE_ERROR_INVALID_PROTOCOLPROFILE )
{
if
(
my
$ACS
=
eval
{
$login
->request->AssertionConsumerServiceURL } )
{
$self
->logger->error(
"Requested AssertionConsumerServiceURL $ACS"
.
" might be missing from registered metadata"
);
}
else
{
$self
->logger->error(
"Make sure AssertionConsumerServiceURL from the "
.
" SAML requests is defined in registered metadata"
);
}
return
$self
->_failAuthnRequest(
$req
,
msg
=>
"SSO: Fail to process authentication request"
);
}
else
{
return
$self
->_failAuthnRequest(
$req
,
msg
=>
"SSO: Fail to process authentication request"
);
}
}
my
$sp
=
$request
?
$login
->remote_providerID() :
$idp_initiated_sp
;
$self
->logger->debug(
"Found entityID $sp in SAML message"
);
my
$spConfKey
=
$self
->spList->{
$sp
}->{confKey};
unless
(
$spConfKey
) {
return
$self
->_failAuthnRequest(
$req
,
msg
=>
"$sp do not match any SP in configuration"
,
res
=> PE_UNKNOWNPARTNER,
logInfo
=> {
sp
=>
$spConfKey
,
entity_id
=>
$sp
,
},
userLogger
=> 1,
);
}
$self
->logger->debug(
"$sp match $spConfKey SP in configuration"
);
my
$checkSSOMessageSignature
=
$self
->spOptions->{
$sp
}
->{samlSPMetaDataOptionsCheckSSOMessageSignature};
if
(
$checkSSOMessageSignature
) {
$self
->forceSignatureVerification(
$login
);
my
$lasso_error
;
if
(
$artifact
) {
$result
=
$self
->processArtResponseMsg(
$login
,
$request
);
}
else
{
(
$result
,
$lasso_error
) =
$self
->processAuthnRequestMsgWithError(
$login
,
$request
);
}
unless
(
$result
) {
return
$self
->_failAuthnRequest(
$req
,
msg
=> (
"Could not verify signature of "
.
"incoming SSO request from $spConfKey"
),
res
=> PE_SAML_SIGNATURE_ERROR,
logInfo
=> {
sp
=>
$spConfKey
,
entity_id
=>
$sp
,
},
);
}
else
{
$self
->logger->debug(
"Signature is valid"
);
}
}
else
{
$self
->logger->debug(
"Message signature will not be checked"
);
}
my
$h
=
$self
->p->processHook(
$req
,
'samlGotAuthnRequest'
,
$login
);
return
$self
->_failAuthnRequest(
$req
,
msg
=>
'samlGotAuthnRequest hook failed'
,
res
=>
$h
,
logInfo
=> {
sp
=>
$spConfKey
,
entity_id
=>
$sp
,
},
)
if
(
$h
!= PE_OK );
$req
->env->{llng_saml_sp} =
$sp
;
$req
->env->{llng_saml_spconfkey} =
$spConfKey
;
if
(
$login
->request ) {
my
$acs
=
$login
->request->AssertionConsumerServiceURL;
if
(
$acs
) {
$req
->env->{llng_saml_acs} =
$acs
;
$self
->logger->debug(
"Using AssertionConsumerServiceURL $acs"
);
}
}
if
(
my
$rule
=
$self
->spRules->{
$spConfKey
} ) {
unless
(
$rule
->(
$req
,
$req
->sessionInfo ) ) {
return
$self
->_failAuthnRequest(
$req
,
msg
=> (
'User '
.
$req
->sessionInfo->{
$self
->conf->{whatToTrace}
}
.
" is not authorized to access to $spConfKey"
),
res
=> PE_UNAUTHORIZEDPARTNER,
userLogger
=> 1,
logInfo
=> {
sp
=>
$spConfKey
,
entity_id
=>
$sp
,
},
);
}
}
my
$nameIDFormat
;
if
(
$login
->request()->NameIDPolicy ) {
$nameIDFormat
=
$login
->request()->NameIDPolicy->Format();
$self
->logger->debug(
"Get NameID format $nameIDFormat from request"
);
}
my
$nameIDFormatKey
=
$self
->spOptions->{
$sp
}->{samlSPMetaDataOptionsNameIDFormat}
||
"email"
;
if
( !
$nameIDFormat
or
$nameIDFormat
eq
$self
->getNameIDFormat(
"unspecified"
) )
{
$nameIDFormat
=
$self
->getNameIDFormat(
$nameIDFormatKey
);
eval
{
if
( !
$login
->request()->NameIDPolicy ) {
$login
->request()
->NameIDPolicy( Lasso::Samlp2NameIDPolicy->new() );
}
$login
->request()->NameIDPolicy()->Format(
$nameIDFormat
);
};
if
($@) {
$self
->logger->
warn
(
"Could not update NameIDPolicy in request: $@"
);
}
}
if
(
$login
->request()->NameIDPolicy ) {
if
(
$nameIDFormat
eq
$self
->getNameIDFormat(
"transient"
)
or
$nameIDFormat
eq
$self
->getNameIDFormat(
"persistent"
) )
{
$self
->logger->debug(
"Force AllowCreate flag in NameIDPolicy"
);
eval
{
$login
->request()->NameIDPolicy()->AllowCreate(1); };
}
}
unless
(
$self
->validateRequestMsg(
$login
, 1, 1 ) ) {
return
$self
->_failAuthnRequest(
$req
,
msg
=>
"Unable to validate SSO request message"
,
logInfo
=> {
sp
=>
$spConfKey
,
entity_id
=>
$sp
,
},
);
}
$self
->logger->debug(
"SSO: authentication request is valid"
);
my
$spAuthnLevel
=
$self
->spOptions->{
$sp
}->{samlSPMetaDataOptionsAuthnLevel} || 0;
my
$force_authn
;
eval
{
$force_authn
=
$login
->request()->ForceAuthn(); };
if
($@) {
$self
->logger->
warn
(
"Unable to get ForceAuthn flag, set it to false"
);
$force_authn
= 0;
}
$self
->logger->debug(
"Found ForceAuthn flag with value $force_authn"
);
if
(
$force_authn
and (
time
-
$req
->sessionInfo->{_utime} >
$self
->conf->{portalForceAuthnInterval} )
)
{
$self
->userLogger->info(
"SAML SP $spConfKey ask to refresh session of "
.
$req
->sessionInfo->{
$self
->conf->{whatToTrace} } );
$req
->pdata->{targetAuthnLevel} =
$spAuthnLevel
;
return
$self
->reAuth(
$req
);
}
unless
(
$req
->data->{_proxiedRequest} ) {
if
( !
$self
->checkDestination(
$login
->request,
$url
) ) {
return
$self
->_failAuthnRequest(
$req
,
msg
=>
"Invalid destination"
,
res
=> PE_SAML_DESTINATION_ERROR,
logInfo
=> {
sp
=>
$spConfKey
,
entity_id
=>
$sp
,
},
);
}
}
my
$authenticationLevel
=
$req
->{sessionInfo}->{authenticationLevel} || 0;
if
(
$authenticationLevel
<
$spAuthnLevel
) {
$self
->logger->debug(
"Insufficient authentication level for service $spConfKey"
.
" (has: $authenticationLevel, want: $spAuthnLevel)"
);
$req
->pdata->{targetAuthnLevel} =
$spAuthnLevel
;
return
$self
->upgradeAuth(
$req
);
}
$authn_context
=
$self
->authnLevel2authnContext(
$authenticationLevel
);
$self
->logger->debug(
"Authentication context is $authn_context"
);
my
$notOnOrAfterTimeout
=
$self
->spOptions->{
$sp
}
->{samlSPMetaDataOptionsNotOnOrAfterTimeout};
unless
(
$self
->buildAssertion(
$req
,
$login
,
$authn_context
,
$notOnOrAfterTimeout
)
)
{
return
$self
->_failAuthnRequest(
$req
,
msg
=>
"Unable to build assertion"
,
logInfo
=> {
sp
=>
$spConfKey
,
entity_id
=>
$sp
,
},
);
}
$self
->logger->debug(
"SSO: assertion is built"
);
my
$nameIDFormatConfiguration
= {
$self
->getNameIDFormat(
"email"
) =>
'samlNameIDFormatMapEmail'
,
$self
->getNameIDFormat(
"x509"
) =>
'samlNameIDFormatMapX509'
,
$self
->getNameIDFormat(
"windows"
) =>
'samlNameIDFormatMapWindows'
,
$self
->getNameIDFormat(
"kerberos"
) =>
'samlNameIDFormatMapKerberos'
,
};
my
$nameIDSessionKey
=
$nameIDFormatConfiguration
->{
$nameIDFormat
}
?
$self
->conf->{
$nameIDFormatConfiguration
->{
$nameIDFormat
} }
:
''
;
if
(
$self
->spOptions->{
$sp
}->{samlSPMetaDataOptionsNameIDSessionKey}
)
{
$nameIDSessionKey
=
$self
->spOptions->{
$sp
}
->{samlSPMetaDataOptionsNameIDSessionKey};
}
my
$nameIDContent
;
if
(
$nameIDSessionKey
and
$self
->spMacros->{
$spConfKey
}->{
$nameIDSessionKey
} )
{
$nameIDContent
=
$self
->spMacros->{
$spConfKey
}->{
$nameIDSessionKey
}
->(
$req
,
$req
->{sessionInfo} );
}
elsif
(
$nameIDSessionKey
and (
defined
$req
->{sessionInfo}->{
$nameIDSessionKey
} ) )
{
$nameIDContent
=
$self
->p->getFirstValue(
$req
->{sessionInfo}->{
$nameIDSessionKey
} );
}
if
(
$nameIDFormat
eq
$self
->getNameIDFormat(
"entity"
) ) {
$nameIDContent
=
$self
->getMetaDataURL(
"samlEntityID"
, 0, 1 );
}
if
(
$nameIDFormat
eq
$self
->getNameIDFormat(
"transient"
) ) {
eval
{
my
@assert
=
$login
->response->Assertion;
$nameIDContent
=
$assert
[0]->Subject->NameID->content;
};
}
if
(
$login
->nameIdentifier ) {
$login
->nameIdentifier->Format(
$nameIDFormat
);
$login
->nameIdentifier->content(
$nameIDContent
)
if
$nameIDContent
;
}
else
{
my
$nameIdentifier
= Lasso::Saml2NameID->new();
$nameIdentifier
->Format(
$nameIDFormat
);
$nameIdentifier
->content(
$nameIDContent
)
if
$nameIDContent
;
$login
->nameIdentifier(
$nameIdentifier
);
}
$self
->logger->debug(
"NameID Format is "
.
$login
->nameIdentifier->Format );
$self
->logger->debug(
"NameID Content is "
. (
$login
->nameIdentifier->content ||
""
) );
my
@attributes
;
my
%log_attributes
;
foreach
(
keys
%{
$self
->spAttributes->{
$sp
} } ) {
my
(
$mandatory
,
$name
,
$format
,
$friendly_name
) =
split
( /;/,
$self
->spAttributes->{
$sp
}->{
$_
} );
next
unless
$name
;
my
$value
;
if
(
$self
->spMacros->{
$spConfKey
}->{
$_
} ) {
$value
=
$self
->spMacros->{
$spConfKey
}->{
$_
}
->(
$req
,
$req
->{sessionInfo} );
}
else
{
$value
=
$req
->{sessionInfo}->{
$_
};
}
unless
(
defined
$value
) {
if
(
$mandatory
) {
return
$self
->_failAuthnRequest(
$req
,
msg
=> (
"Session key $_ is required to set"
.
" SAML $name attribute ($sp)"
),
res
=> PE_ISSUERMISSINGREQATTR,
logInfo
=> {
sp
=>
$spConfKey
,
entity_id
=>
$sp
,
},
);
}
else
{
$self
->logger->debug(
"SAML2 attribute $name has no value"
.
" but is not mandatory ($sp), skip it"
);
next
;
}
}
$self
->logger->debug(
"SAML2 attribute $name will be set"
.
" with $_ session key ($sp)"
);
my
$attribute
=
$self
->createAttribute(
$name
,
$format
,
$friendly_name
);
unless
(
$attribute
) {
return
$self
->_failAuthnRequest(
$req
,
msg
=>
"Unable to create a new SAML attribute"
,
logInfo
=> {
sp
=>
$spConfKey
,
entity_id
=>
$sp
,
},
);
}
my
@values
=
split
$self
->conf->{multiValuesSeparator},
$value
;
my
@saml2values
;
foreach
(
@values
) {
my
$saml2value
=
$self
->createAttributeValue(
$_
,
$self
->spOptions->{
$sp
}
->{samlSPMetaDataOptionsForceUTF8} );
unless
(
$saml2value
) {
$self
->checkLassoError($@);
return
$self
->_failAuthnRequest(
$req
,
msg
=>
"Unable to create a new SAML attribute value"
,
logInfo
=> {
sp
=>
$spConfKey
,
entity_id
=>
$sp
,
},
);
}
push
@saml2values
,
$saml2value
;
$self
->logger->debug(
"Push $_ in SAML attribute $name"
);
}
$attribute
->AttributeValue(
@saml2values
);
push
@attributes
,
$attribute
;
$log_attributes
{
$friendly_name
||
$name
} = [
@values
];
}
my
@response_assertions
=
$login
->response->Assertion;
unless
(
$response_assertions
[0] ) {
return
$self
->_failAuthnRequest(
$req
,
msg
=>
"Unable to get response assertion"
,
logInfo
=> {
sp
=>
$spConfKey
,
entity_id
=>
$sp
,
},
);
}
if
(
$domain
) {
my
$original_issuer
=
$login
->response->Issuer->content;
$self
->logger->debug(
"Add domain $domain to Issuer $original_issuer"
);
my
$new_issuer
=
$original_issuer
.
"?domain=$domain"
;
$login
->response->Issuer->content(
$new_issuer
);
$login
->response->Assertion->Issuer->content(
$new_issuer
);
}
$response_assertions
[0]
->set_subject_name_id(
$login
->nameIdentifier );
my
$oneTimeUse
=
$self
->spOptions->{
$sp
}->{samlSPMetaDataOptionsOneTimeUse} // 0;
my
$conditionNotOnOrAfter
=
$notOnOrAfterTimeout
||
"86400"
;
eval
{
$response_assertions
[0]
->set_basic_conditions( 60,
$conditionNotOnOrAfter
,
$oneTimeUse
);
};
if
($@) {
$self
->logger->debug(
"Basic conditions not set: $@"
);
}
if
(
scalar
@attributes
) {
my
$attribute_statement
;
eval
{
$attribute_statement
=
Lasso::Saml2AttributeStatement->new();
};
if
($@) {
$self
->checkLassoError($@);
return
$self
->_failAuthnRequest(
$req
,
msg
=>
"Unable to create attribute statement"
,
logInfo
=> {
sp
=>
$spConfKey
,
entity_id
=>
$sp
,
},
);
}
$attribute_statement
->Attribute(
@attributes
);
my
@attributes_statement
= (
$attribute_statement
);
$response_assertions
[0]
->AttributeStatement(
@attributes_statement
);
}
my
@authn_statements
=
$response_assertions
[0]->AuthnStatement();
my
$sessionIndexSession
=
$self
->getSamlSession();
if
( !
$sessionIndexSession
) {
return
$self
->_failAuthnRequest(
$req
,
msg
=>
"Unable to create SAML Session"
,
res
=> PE_SAML_SESSION_ERROR,
logInfo
=> {
sp
=>
$spConfKey
,
entity_id
=>
$sp
,
},
);
}
$sessionIndexSession
->update(
{
'_utime'
=>
time
,
'_saml_id'
=>
$session_id
} );
my
$sessionIndex
=
$sessionIndexSession
->id;
$authn_statements
[0]->SessionIndex(
$sessionIndex
);
$self
->logger->debug(
"Set sessionIndex $sessionIndex (linked to session $session_id)"
);
my
$sessionNotOnOrAfterTimeout
=
$self
->spOptions->{
$sp
}
->{samlSPMetaDataOptionsSessionNotOnOrAfterTimeout};
$sessionNotOnOrAfterTimeout
||=
$self
->conf->{timeout};
my
$timeout
=
$time
+
$sessionNotOnOrAfterTimeout
;
my
$sessionNotOnOrAfter
=
$self
->timestamp2samldate(
$timeout
);
$authn_statements
[0]->SessionNotOnOrAfter(
$sessionNotOnOrAfter
);
$self
->logger->debug(
"Set sessionNotOnOrAfter $sessionNotOnOrAfter"
);
$response_assertions
[0]->AuthnStatement(
@authn_statements
);
$login
->response->Assertion(
@response_assertions
);
my
$signSSOMessage
=
$self
->spOptions->{
$sp
}->{samlSPMetaDataOptionsSignSSOMessage}
// -1;
if
(
$signSSOMessage
== 0 ) {
$self
->logger->debug(
"SSO response will not be signed"
);
$self
->disableSignature(
$login
);
}
elsif
(
$signSSOMessage
== 1 ) {
$self
->logger->debug(
"SSO response will be signed"
);
$self
->forceSignature(
$login
);
}
else
{
$self
->logger->debug(
"SSO response signature according to metadata"
);
}
$h
=
$self
->p->processHook(
$req
,
'samlBuildAuthnResponse'
,
$login
);
return
$self
->_failAuthnRequest(
$req
,
msg
=>
'samlBuildAuthnResponse hook failed'
,
res
=>
$h
,
logInfo
=> {
sp
=>
$spConfKey
,
entity_id
=>
$sp
,
},
)
if
(
$h
!= PE_OK );
$protocolProfile
=
$login
->protocolProfile();
if
(
$artifact
or
$protocolProfile
==
Lasso::Constants::LOGIN_PROTOCOL_PROFILE_BRWS_ART )
{
$artifact
= 1;
if
(
$method
==
$self
->getHttpMethod(
"post"
)
||
$method
==
$self
->getHttpMethod(
"artifact-post"
) )
{
$artifact_method
=
$self
->getHttpMethod(
"artifact-post"
);
}
else
{
$artifact_method
=
$self
->getHttpMethod(
"artifact-get"
);
}
}
if
(
$protocolProfile
==
Lasso::Constants::LOGIN_PROTOCOL_PROFILE_BRWS_ART )
{
unless
(
$self
->buildArtifactMsg(
$login
,
$artifact_method
) ) {
return
$self
->_failAuthnRequest(
$req
,
msg
=>
"Unable to build SSO artifact response message"
,
res
=> PE_SAML_ART_ERROR,
logInfo
=> {
sp
=>
$spConfKey
,
entity_id
=>
$sp
,
},
);
}
$self
->logger->debug(
"SSO: artifact response is built"
);
my
$artifact_id
=
$login
->get_artifact;
my
$artifact_message
=
$login
->get_artifact_message;
$self
->storeArtifact(
$artifact_id
,
$artifact_message
,
$session_id
);
}
else
{
unless
(
$self
->buildAuthnResponseMsg(
$login
) ) {
return
$self
->_failAuthnRequest(
$req
,
msg
=>
"Unable to build SSO response message"
,
logInfo
=> {
sp
=>
$spConfKey
,
entity_id
=>
$sp
,
},
);
}
$self
->logger->debug(
"SSO: authentication response is built"
);
}
if
(
$login
->is_identity_dirty ) {
$self
->logger->debug(
"Save Lasso identity in session"
);
$self
->p->updatePersistentSession(
$req
,
{
$self
->
liDump
=>
$login
->get_identity->
dump
},
undef
,
$session_id
);
}
if
(
$login
->is_session_dirty ) {
$self
->logger->debug(
"Save Lasso session in session"
);
$self
->p->updateSession(
$req
,
{
$self
->
lsDump
=>
$login
->get_session->
dump
},
$session_id
);
}
my
$nameid
=
$login
->nameIdentifier;
$self
->logger->debug(
"Store NameID "
.
$nameid
->
dump
.
" and SessionIndex $sessionIndex for session $session_id"
);
my
$infos
;
$infos
->{type} =
'saml'
;
$infos
->{_utime} =
$time
;
$infos
->{_saml_id} =
$session_id
;
$infos
->{_nameID} =
$nameid
->
dump
;
$infos
->{_sessionIndex} =
$sessionIndex
;
my
$samlSessionInfo
=
$self
->getSamlSession(
undef
,
$infos
);
if
( !
$samlSessionInfo
) {
return
$self
->_failAuthnRequest(
$req
,
msg
=>
"Could not get SAML session"
,
res
=> PE_SAML_SESSION_ERROR,
logInfo
=> {
sp
=>
$spConfKey
,
entity_id
=>
$sp
,
},
);
}
my
$saml_session_id
=
$samlSessionInfo
->id;
$self
->logger->debug(
"Link session $session_id to SAML session $saml_session_id"
);
$self
->p->registerProtectedAppAccess(
$req
,
$req
->{sessionInfo}->{
$self
->conf->{whatToTrace} },
"saml:$spConfKey"
);
if
(
$self
->conf->{samlCommonDomainCookieActivation}
and
$self
->conf->{samlCommonDomainCookieWriter} )
{
my
$cdc_idp
=
$self
->getMetaDataURL(
"samlEntityID"
, 0, 1 );
$self
->logger->debug(
"Will register IDP $cdc_idp in Common Domain Cookie"
);
my
$cdc_writer_url
=
$self
->conf->{samlCommonDomainCookieWriter};
$cdc_writer_url
.= (
$self
->conf->{samlCommonDomainCookieWriter} =~ /\?/
?
'&idp='
.
$cdc_idp
:
'?url='
.
$cdc_idp
);
my
$cdc_iframe
=
qq'<iframe src="$cdc_writer_url"'
.
' alt="Common Dommain Cookie" marginwidth="0"'
.
' marginheight="0" scrolling="no" class="hiddenFrame"'
.
' width="0" height="0" frameborder="0"></iframe>'
;
$req
->info(
$self
->loadTemplate(
$req
,
'simpleInfo'
,
params
=> {
trspan
=>
'updateCdc'
}
)
.
$cdc_iframe
);
}
my
$user
=
$req
->{sessionInfo}->{
$self
->conf->{whatToTrace} };
my
$nameIDLog
=
''
;
foreach
my
$format
(
qw(persistent transient)
) {
if
(
$login
->nameIdentifier->Format eq
$self
->getNameIDFormat(
$format
) )
{
$nameIDLog
=
" with $format NameID "
.
$login
->nameIdentifier->content;
last
;
}
}
my
$name_id_content
=
$login
->nameIdentifier->content;
my
$attr_str
= (
%log_attributes
? (
" with attributes "
.
join
(
','
,
sort
(
keys
(
%log_attributes
) ) ) )
:
""
);
if
( (
!
$artifact
and
$protocolProfile
eq
Lasso::Constants::LOGIN_PROTOCOL_PROFILE_BRWS_POST
)
or (
$artifact
and
$artifact_method
==
$self
->getHttpMethod(
"artifact-post"
) )
)
{
$self
->auditLog(
$req
,
code
=>
"ISSUER_SAML_LOGIN_SUCCESS"
,
entity_id
=>
$sp
,
sp
=>
$spConfKey
,
message
=> (
'User '
.
$req
->sessionInfo->{
$self
->conf->{whatToTrace} }
.
" is authorized to access to $sp."
.
" SAML authentication response sent to"
.
" SAML SP $spConfKey for $user$nameIDLog$attr_str"
),
user
=>
$user
,
saml_name_id
=>
$name_id_content
,
attributes
=> \
%log_attributes
,
);
my
$sso_url
=
$login
->msg_url;
my
$sso_body
=
$login
->msg_body;
$req
->postUrl(
$sso_url
);
if
(
$artifact_method
and
$artifact_method
==
$self
->getHttpMethod(
"artifact-post"
) )
{
$req
->{postFields} = {
'SAMLart'
=>
$sso_body
};
}
else
{
$req
->{postFields} = {
'SAMLResponse'
=>
$sso_body
};
}
if
(
$relaystate
) {
$req
->{postFields}->{
'RelayState'
} =
encode_entities(
$relaystate
);
$req
->data->{safeHiddenFormValues}->{RelayState} = 1;
}
$req
->steps( [
'autoPost'
] );
return
PE_OK;
}
if
(
$protocolProfile
eq
Lasso::Constants::LOGIN_PROTOCOL_PROFILE_REDIRECT or
$artifact
)
{
$self
->auditLog(
$req
,
code
=>
"ISSUER_SAML_LOGIN_SUCCESS"
,
entity_id
=>
$sp
,
sp
=>
$spConfKey
,
message
=> (
'User '
.
$req
->sessionInfo->{
$self
->conf->{whatToTrace} }
.
" is authorized to access to $sp."
.
" SAML authentication response sent to"
.
" SAML SP $spConfKey for $user$nameIDLog$attr_str"
),
user
=>
$user
,
saml_name_id
=>
$name_id_content
,
attributes
=> \
%log_attributes
,
);
my
$sso_url
=
$login
->msg_url;
$self
->logger->debug(
"Redirect user to $sso_url"
);
$req
->{urldc} =
$sso_url
;
$req
->mustRedirect(1);
$req
->steps( [] );
return
PE_OK;
}
}
elsif
(
$response
) {
$self
->logger->debug(
"Authentication responses are not managed by this module"
);
return
PE_OK;
}
else
{
$self
->logger->debug(
"No request or response found"
);
return
PE_OK;
}
}
$self
->logger->debug(
"Not an issuer request $url"
);
return
PE_OK;
}
sub
_failAuthnRequest {
my
(
$self
,
$req
,
%params
) =
@_
;
my
$reason
=
$params
{
'msg'
} ?
": $params{'msg'}"
:
""
;
my
$res
=
$params
{res} || PE_SAML_SSO_ERROR;
my
$user
=
$req
->sessionInfo->{
$self
->conf->{whatToTrace} };
my
%logInfo
= %{
$params
{logInfo} || {} };
$self
->auditLog(
$req
,
code
=>
"ISSUER_SAML_LOGIN_FAILED"
,
message
=> (
"SAML login failed"
.
$reason
),
(
$params
{
'msg'
} ? (
reason
=>
$params
{
'msg'
} ) : () ),
portal_error
=> portalConsts->{
$res
},
user
=>
$user
,
%logInfo
,
);
return
$res
;
}
sub
artifactServer {
my
(
$self
,
$req
) =
@_
;
$self
->logger->debug(
"URL "
.
$req
->uri
.
" detected as an artifact resolution service URL"
);
my
$art_request
=
$req
->content;
my
$art_response
;
my
$login
=
$self
->createLogin(
$self
->lassoServer );
unless
(
$self
->processArtRequestMsg(
$login
,
$art_request
) ) {
return
$self
->p->sendError(
$req
,
'Unable to process artifact request message'
, 400 );
}
unless
(
$self
->checkDestination(
$login
->request,
$req
->uri ) ) {
return
$self
->p->sendError(
$req
,
'Bad request'
, 400 );
}
unless
(
$art_response
=
$self
->createArtifactResponse(
$req
,
$login
) ) {
return
$self
->p->sendError(
$req
,
"Unable to create artifact response message"
, 400 );
}
$self
->{SOAPMessage} =
$art_response
;
$self
->logger->debug(
"Send SOAP Message: $art_response"
);
return
[
200,
[
'Content-Type'
=>
'text/xml'
,
'Content-Length'
=>
length
(
$art_response
)
],
[
$art_response
]
];
}
sub
soapSloServer {
my
(
$self
,
$req
) =
@_
;
my
$url
=
$req
->uri;
my
$request_method
=
$req
->param(
'issuerMethod'
) ||
$req
->method;
my
$content_type
=
$req
->content_type();
$self
->logger->debug(
"URL $url detected as an SLO URL"
);
my
(
$request
,
$response
,
$method
,
$relaystate
,
$artifact
) =
$self
->checkMessage(
$req
,
$url
,
$request_method
,
$content_type
,
"logout"
);
my
$logout
=
$self
->createLogout(
$self
->lassoServer );
$self
->disableSignatureVerification(
$logout
);
if
(
$request
) {
unless
(
$self
->processLogoutRequestMsg(
$logout
,
$request
) ) {
return
$self
->p->sendError(
$req
,
"SLO: Fail to process logout request"
, 400 );
}
$self
->logger->debug(
"SLO: Logout request is valid"
);
my
$h
=
$self
->p->processHook(
$req
,
'samlGotLogoutRequest'
,
$logout
);
if
(
$h
!= PE_OK ) {
return
$self
->p->sendError(
$req
,
"SLO: samlGotLogoutRequest hook returned error"
, 400 );
}
unless
(
$method
eq
$self
->getHttpMethod(
'soap'
) ) {
return
$self
->p->sendError(
$req
,
"Only SOAP requests allowed here"
, 400 );
}
my
$sp
=
$logout
->remote_providerID();
$self
->logger->debug(
"Found entityID $sp in SAML message"
);
my
$spConfKey
=
$self
->spList->{
$sp
}->{confKey};
unless
(
$spConfKey
) {
return
$self
->p->sendError(
$req
,
"$sp do not match any SP in configuration"
, 400 );
}
$self
->logger->debug(
"$sp match $spConfKey SP in configuration"
);
my
$checkSLOMessageSignature
=
$self
->spOptions->{
$sp
}
->{samlSPMetaDataOptionsCheckSLOMessageSignature};
if
(
$checkSLOMessageSignature
) {
$self
->forceSignatureVerification(
$logout
);
unless
(
$self
->processLogoutRequestMsg(
$logout
,
$request
) ) {
return
$self
->p->sendError(
$req
,
"Signature is not valid"
,
400 );
}
else
{
$self
->logger->debug(
"Signature is valid"
);
}
}
else
{
$self
->logger->debug(
"Message signature will not be checked"
);
}
my
$saml_request
=
$logout
->request();
unless
(
$saml_request
) {
return
$self
->p->sendError(
$req
,
"No SAML request found"
, 400 );
}
return
$self
->sendSLOSoapErrorResponse(
$req
,
$logout
,
$method
)
unless
(
$self
->checkDestination(
$saml_request
,
$url
) );
my
$session_index
;
eval
{
$session_index
=
$logout
->request()->SessionIndex; };
unless
(
defined
$session_index
) {
$self
->p->sendError(
$req
,
"No session index in SLO request from $spConfKey SP"
, 400 );
}
my
$sessionIndexSession
=
$self
->getSamlSession(
$session_index
);
return
$self
->p->sendError(
$req
,
'SAML session not found'
, 400 )
unless
$sessionIndexSession
;
my
$local_session_id
=
$sessionIndexSession
->data->{_saml_id};
$sessionIndexSession
->remove;
$self
->logger->debug(
"Get session id $local_session_id"
.
" (from session index $session_index)"
);
my
$local_session
=
$self
->p->getApacheSession(
$local_session_id
);
unless
(
$local_session
) {
return
$self
->p->sendError(
$req
,
"No local session found"
, 400 );
}
my
$session
=
$local_session
->data->{
$self
->lsDump };
my
$identity
=
$local_session
->data->{
$self
->liDump };
if
(
$session
) {
unless
(
$self
->setSessionFromDump(
$logout
,
$session
) ) {
return
$self
->p->sendError(
$req
,
"Unable to load Lasso Session"
, 400 );
}
$self
->logger->debug(
"Lasso Session loaded"
);
}
if
(
$identity
) {
unless
(
$self
->setIdentityFromDump(
$logout
,
$identity
) ) {
return
$self
->p->sendError(
$req
,
"Unable to load Lasso Identity"
, 400 );
}
$self
->logger->debug(
"Lasso Identity loaded"
);
}
unless
(
$self
->deleteSAMLSecondarySessions(
$local_session_id
) ) {
return
$self
->p->sendError(
$req
,
"Fail to delete SAML sessions"
,
400 );
}
unless
(
$self
->p->_deleteSession(
$req
,
$local_session
) ) {
return
$self
->p->sendError(
$req
,
"Fail to delete session $local_session_id"
, 400 );
}
unless
(
$self
->validateLogoutRequest(
$logout
) ) {
return
$self
->p->sendError(
$req
,
"SLO request is not valid"
, 400 );
}
$self
->resetProviderIdIndex(
$logout
);
while
(
my
$providerID
=
$self
->getNextProviderId(
$logout
) ) {
my
(
$rstatus
,
$rmethod
,
$rinfo
) =
$self
->sendLogoutRequestToProvider(
$logout
,
$providerID
,
$self
->getHttpMethod(
'soap'
), 0 );
if
(
$rstatus
) {
$self
->logger->debug(
"SOAP SLO successful on $providerID"
);
}
else
{
$self
->logger->debug(
"SOAP SLO error on $providerID"
);
}
}
if
(
$relaystate
) {
$logout
->msg_relayState(
$relaystate
);
$self
->logger->debug(
"Set $relaystate in RelayState"
);
}
my
$signSLOMessage
=
$self
->spOptions->{
$sp
}->{samlSPMetaDataOptionsSignSLOMessage} // 0;
if
(
$signSLOMessage
== 0 ) {
$self
->logger->debug(
"SLO response will not be signed"
);
$self
->disableSignature(
$logout
);
}
elsif
(
$signSLOMessage
== 1 ) {
$self
->logger->debug(
"SLO response will be signed"
);
$self
->forceSignature(
$logout
);
}
else
{
$self
->logger->debug(
"SLO response signature according to metadata"
);
}
$h
=
$self
->p->processHook(
$req
,
'samlBuildLogoutResponse'
,
$logout
);
if
(
$h
!= PE_OK ) {
return
$self
->p->sendError(
$req
,
"SLO: samlBuildLogoutResponse hook returned error"
, 400 );
}
unless
(
$self
->buildLogoutResponseMsg(
$logout
) ) {
$self
->logger->error(
"Unable to build SLO response for $spConfKey"
);
return
$self
->p->sendError(
$req
,
'Unable to build SLO response'
,
400 );
}
my
$slo_body
=
$logout
->msg_body;
return
[
200,
[
'Content-Type'
=>
'text/xml'
,
'Content-Length'
=>
length
(
$slo_body
)
],
[
$slo_body
]
];
}
}
sub
logout {
my
(
$self
,
$req
) =
@_
;
return
PE_OK
if
(
$req
->data->{samlSLOCalled} );
my
$session_id
=
$req
->{sessionInfo}->{_session_id} ||
$req
->id;
unless
(
$self
->deleteSAMLSecondarySessions(
$session_id
) ) {
$self
->logger->error(
"Fail to delete SAML sessions"
);
}
my
$logout
=
$self
->createLogout(
$self
->lassoServer );
my
$session
=
$req
->{sessionInfo}->{
$self
->lsDump };
my
$identity
=
$req
->{sessionInfo}->{
$self
->liDump };
if
(
$session
) {
unless
(
$self
->setSessionFromDump(
$logout
,
$session
) ) {
$self
->logger->error(
"Unable to load Lasso Session"
);
return
PE_SLO_ERROR;
}
$self
->logger->debug(
"Lasso Session loaded"
);
}
else
{
$self
->logger->debug(
'No SAML session available into this session'
);
return
PE_OK;
}
if
(
$identity
) {
unless
(
$self
->setIdentityFromDump(
$logout
,
$identity
) ) {
$self
->logger->error(
"Unable to load Lasso Identity"
);
return
PE_SLO_ERROR;
}
$self
->logger->debug(
"Lasso Identity loaded"
);
}
if
(
$self
->sendLogoutRequestToProviders(
$req
,
$logout
) ) {
$req
->urldc(
$self
->p->buildUrl(
$req
->portal, {
logout
=> 1 } ) );
return
PE_OK;
}
return
PE_OK;
}
sub
sloRelaySoap {
my
(
$self
,
$req
) =
@_
;
$self
->logger->debug(
"URL "
.
$req
->uri .
" detected as a SOAP relay service URL"
);
my
$relayID
;
unless
(
$relayID
=
$req
->param(
'relay'
) ) {
$self
->logger->error(
"No relayID detected"
);
return
$self
->imgnok(
$req
);
}
my
$relayInfos
=
$self
->getSamlSession(
$relayID
);
unless
(
$relayInfos
) {
$self
->logger->error(
"Could not get relay session $relayID"
);
return
$self
->imgnok(
$req
);
}
$self
->logger->debug(
"Found relay session $relayID"
);
my
$logout
;
unless
(
$logout
=
$self
->createLogout(
$self
->lassoServer ) ) {
$self
->logger->error(
"Could not rebuild logout object"
);
return
$self
->imgnok(
$req
);
}
my
$session
=
$relayInfos
->data->{
$self
->lsDump };
my
$identity
=
$relayInfos
->data->{
$self
->liDump };
my
$providerID
=
$relayInfos
->data->{_providerID};
my
$relayState
=
$relayInfos
->data->{_relayState} //
''
;
my
$spConfKey
=
$self
->spList->{
$providerID
}->{confKey};
if
(
$session
) {
unless
(
$self
->setSessionFromDump(
$logout
,
$session
) ) {
$self
->logger->error(
"Unable to load Lasso Session"
);
return
$self
->imgnok(
$req
);
}
$self
->logger->debug(
"Lasso Session loaded"
);
}
if
(
$identity
) {
unless
(
$self
->setIdentityFromDump(
$logout
,
$identity
) ) {
$self
->logger->error(
"Unable to load Lasso Identity"
);
return
$self
->imgnok(
$req
);
}
$self
->logger->debug(
"Lasso Identity loaded"
);
}
my
(
$rstatus
,
$rmethod
,
$rinfo
) =
$self
->sendLogoutRequestToProvider(
$req
,
$logout
,
$providerID
,
Lasso::Constants::HTTP_METHOD_SOAP,
undef
,
$relayState
);
unless
(
$rstatus
) {
$self
->logger->error(
"Fail to process SOAP logout request to $providerID"
);
return
$self
->imgnok(
$req
);
}
my
$sloStatusSessionInfos
=
$self
->getSamlSession(
$relayState
,
{
$spConfKey
=> 1,
_utime
=>
time
() } );
if
(
$sloStatusSessionInfos
) {
$self
->logger->debug(
"Store SLO status for $spConfKey in session $relayState"
);
}
else
{
$self
->logger->
warn
(
"Unable to store SLO status for $spConfKey in session $relayState"
);
}
$relayInfos
->remove();
$self
->logger->debug(
"Display OK status for SLO on $spConfKey"
);
return
$self
->imgok(
$req
);
}
sub
sloRelayPost {
my
(
$self
,
$req
) =
@_
;
$self
->logger->debug(
"URL "
.
$req
->uri .
" detected as a POST relay service URL"
);
my
$relayID
;
unless
(
$relayID
=
$req
->param(
'relay'
) ) {
return
$self
->p->sendError(
$req
,
'No relayID detected'
);
}
my
$relayInfos
=
$self
->getSamlSession(
$relayID
);
unless
(
$relayInfos
) {
return
$self
->p->sendError(
$req
,
"Could not get relay session $relayID"
);
}
$self
->logger->debug(
"Found relay session $relayID"
);
$req
->{postUrl} =
$relayInfos
->data->{url};
$req
->{postFields}->{
'SAMLRequest'
} =
$relayInfos
->data->{body};
if
(
$relayInfos
->data->{relayState} ) {
$req
->{postFields}->{
'RelayState'
} =
encode_entities(
$relayInfos
->data->{relayState} );
$req
->data->{safeHiddenFormValues}->{RelayState} = 1;
}
$relayInfos
->remove();
$req
->frame(1);
return
$self
->p->
do
(
$req
, [
'autoPost'
] );
}
sub
sloRelayTerm {
my
(
$self
,
$req
) =
@_
;
$self
->logger->debug(
"URL "
.
$req
->uri
.
" detected as a SLO Termination relay service URL"
);
my
$relayID
=
$self
->p->getHiddenFormValue(
$req
,
'relay'
,
''
, 0 )
||
$req
->param(
'relay'
);
unless
(
$relayID
) {
return
$self
->p->sendError(
$req
,
'No relayID detected'
);
}
my
$relayInfos
=
$self
->getSamlSession(
$relayID
);
unless
(
$relayInfos
) {
return
$self
->p->sendError(
$req
,
"Could not get relay session $relayID"
);
}
$self
->logger->debug(
"Found relay session $relayID"
);
my
$logout_dump
=
$relayInfos
->data->{_logout};
my
$session_dump
=
$relayInfos
->data->{_session};
my
$method
=
$relayInfos
->data->{_method};
unless
(
$logout_dump
) {
$self
->logger->error(
"Could not get logout dump"
);
return
PE_SLO_ERROR;
}
my
$logout
=
$self
->createLogout(
$self
->lassoServer,
$logout_dump
);
unless
(
$logout
) {
$self
->logger->error(
"Could not build Lasso::Logout"
);
return
PE_SLO_ERROR;
}
unless
(
$session_dump
) {
$self
->logger->error(
"Could not get session dump"
);
return
PE_SLO_ERROR;
}
unless
(
$self
->setSessionFromDump(
$logout
,
$session_dump
) ) {
$self
->logger->error(
"Could not set session from dump"
);
return
PE_SLO_ERROR;
}
my
$session
=
$logout
->get_session();
unless
(
$session
) {
$self
->logger->error(
"Could not get session from logout"
);
return
PE_SLO_ERROR;
}
$self
->resetProviderIdIndex(
$logout
);
while
(
my
$sp
=
$self
->getNextProviderId(
$logout
) ) {
my
$spConfKey
=
$self
->spList->{
$sp
}->{confKey};
my
$status
=
$relayInfos
->data->{
$spConfKey
};
if
(
$status
) {
eval
{
$session
->remove_assertion(
$sp
); };
if
($@) {
$self
->logger->
warn
(
"Unable to remove assertion for $sp"
);
}
else
{
$self
->logger->debug(
"Assertion removed for $sp"
);
}
}
else
{
$self
->logger->debug(
"SLO status was not ok for $sp, assertion not removed"
);
}
}
unless
(
$session
->is_empty() ) {
$self
->setSessionFromDump(
$logout
,
$session
->
dump
);
}
$relayInfos
->remove();
if
(
my
$tmp
=
$self
->sendLogoutResponseToServiceProvider(
$req
,
$logout
,
$method
) )
{
return
$tmp
;
}
else
{
$self
->logger->error(
"Fail to send SLO response"
);
return
PE_SLO_ERROR;
}
}
sub
authSloServer {
my
(
$self
,
$req
) =
@_
;
$self
->p->importHandlerData(
$req
);
return
$self
->sloServer(
$req
);
}
sub
sloResume {
my
(
$self
,
$req
) =
@_
;
my
$ResumeParams
=
$req
->params(
'ResumeParams'
);
unless
(
$ResumeParams
) {
$self
->logger->error(
"Could not find resumption info"
);
return
PE_SLO_ERROR;
}
my
$logoutContextSession
=
$self
->getSamlSession(
$ResumeParams
);
unless
(
$logoutContextSession
) {
$self
->logger->error(
"Could not find logout context session"
);
return
PE_SLO_ERROR;
}
my
$spConfKey
=
$logoutContextSession
->data->{spConfKey};
my
$method
=
$logoutContextSession
->data->{method};
my
$provider_nb
=
$logoutContextSession
->data->{provider_nb};
my
$relayID
=
$logoutContextSession
->data->{relayID};
my
$logout
=
$self
->createLogout(
$self
->lassoServer,
$logoutContextSession
->data->{logout} );
$req
->setInfo(
$logoutContextSession
->data->{info} )
if
$logoutContextSession
->data->{info};
return
$self
->_finishSlo(
$req
,
$logout
,
$method
,
$spConfKey
,
$provider_nb
,
$relayID
);
}
sub
_finishSlo {
my
(
$self
,
$req
,
$logout
,
$method
,
$spConfKey
,
$provider_nb
,
$relayID
) =
@_
;
my
$sp
=
$logout
->remote_providerID;
my
$signSLOMessage
=
''
;
$signSLOMessage
=
$self
->spOptions->{
$sp
}->{samlSPMetaDataOptionsSignSLOMessage}
if
$sp
;
unless
(
$signSLOMessage
) {
$self
->logger->debug(
"Do not sign this SLO response"
);
return
$self
->sendSLOErrorResponse(
$req
,
$logout
,
$method
)
unless
(
$self
->disableSignature(
$logout
) );
}
unless
(
$provider_nb
) {
return
$self
->sendLogoutResponseToServiceProvider(
$req
,
$logout
,
$method
);
}
else
{
$req
->{urldc} =
$self
->p->buildUrl(
$req
->portal,
'saml'
,
'relaySingleLogoutTermination'
);
$self
->p->setHiddenFormValue(
$req
,
'relay'
,
$relayID
,
''
, 0 );
return
$self
->p->
do
(
$req
, [] );
}
}
sub
sloServer {
my
(
$self
,
$req
) =
@_
;
my
$url
=
$req
->uri;
my
$request_method
=
$req
->param(
'issuerMethod'
) ||
$req
->method;
my
$content_type
=
$req
->content_type();
$self
->logger->debug(
"URL $url detected as an SLO URL"
);
my
(
$request
,
$response
,
$method
,
$relaystate
,
$artifact
) =
$self
->checkMessage(
$req
,
$url
,
$request_method
,
$content_type
,
"logout"
);
my
$logout
=
$self
->createLogout(
$self
->lassoServer );
$self
->disableSignatureVerification(
$logout
);
$req
->frame(1);
if
(
$request
) {
unless
(
$self
->processLogoutRequestMsg(
$logout
,
$request
) ) {
return
$self
->p->sendError(
$req
,
"SLO: Fail to process logout request"
, 400 );
}
$self
->logger->debug(
"SLO: Logout request is valid"
);
my
$h
=
$self
->p->processHook(
$req
,
'samlGotLogoutRequest'
,
$logout
);
if
(
$h
!= PE_OK ) {
return
$self
->p->sendError(
$req
,
"SLO: samlGotLogoutRequest hook returned error"
, 400 );
}
my
$sp
=
$logout
->remote_providerID();
$req
->env->{llng_saml_sp} =
$sp
;
$self
->logger->debug(
"Found entityID $sp in SAML message"
);
my
$spConfKey
=
$self
->spList->{
$sp
}->{confKey};
unless
(
$spConfKey
) {
return
$self
->p->sendError(
$req
,
"$sp do not match any SP in configuration"
, 400 );
}
$self
->logger->debug(
"$sp match $spConfKey SP in configuration"
);
$req
->env->{llng_saml_spconfkey} =
$spConfKey
;
my
(
$session
,
$session_index
,
$identity
,
$local_session_id
);
eval
{
$session_index
=
$logout
->request()->SessionIndex; };
unless
(
defined
$session_index
) {
$self
->logger->
warn
(
"No session index in SLO request from $spConfKey SP"
);
}
if
(
$session_index
) {
my
$sessionIndexSession
=
$self
->getSamlSession(
$session_index
);
return
$self
->p->
do
(
$req
, [
sub
{ PE_SESSIONEXPIRED } ] )
unless
$sessionIndexSession
;
$local_session_id
=
$sessionIndexSession
->data->{_saml_id};
$sessionIndexSession
->remove;
$self
->logger->debug(
"Get session id $local_session_id"
.
" (from session index $session_index)"
);
}
else
{
$local_session_id
=
$req
->id;
$self
->logger->debug(
"Get session id $local_session_id (from cookie)"
);
}
if
(
$req
->{sessionInfo} ) {
$session
=
$req
->{sessionInfo}->{
$self
->lsDump };
$identity
=
$req
->{sessionInfo}->{
$self
->liDump };
}
unless
(
$session
) {
my
$local_session
=
$self
->p->getApacheSession(
$local_session_id
);
unless
(
$local_session
) {
$self
->logger->error(
"No local session found"
);
return
$self
->sendSLOErrorResponse(
$req
,
$logout
,
$method
);
}
$session
=
$local_session
->data->{
$self
->lsDump };
$identity
=
$local_session
->data->{
$self
->liDump };
$req
->id(
$local_session
->data->{_session_id} );
$req
->sessionInfo(
$local_session
->data );
$req
->user(
$local_session
->data->{
$self
->conf->{whatToTrace} } );
}
if
(
$session
) {
unless
(
$self
->setSessionFromDump(
$logout
,
$session
) ) {
return
$self
->p->sendError(
$req
,
"Unable to load Lasso Session"
, 400 );
}
$self
->logger->debug(
"Lasso Session loaded"
);
}
if
(
$identity
) {
unless
(
$self
->setIdentityFromDump(
$logout
,
$identity
) ) {
return
$self
->p->sendError(
$req
,
"Unable to load Lasso Identity"
, 400 );
}
$self
->logger->debug(
"Lasso Identity loaded"
);
}
my
$checkSLOMessageSignature
=
$self
->spOptions->{
$sp
}
->{samlSPMetaDataOptionsCheckSLOMessageSignature};
if
(
$checkSLOMessageSignature
) {
$self
->forceSignatureVerification(
$logout
);
unless
(
$self
->processLogoutRequestMsg(
$logout
,
$request
) ) {
return
$self
->p->sendError(
$req
,
"Signature is not valid"
,
400 );
}
else
{
$self
->logger->debug(
"Signature is valid"
);
}
}
else
{
$self
->logger->debug(
"Message signature will not be checked"
);
}
return
$self
->sendSLOErrorResponse(
$req
,
$logout
,
$method
)
unless
(
$self
->checkDestination(
$logout
->request,
$url
) );
unless
(
$self
->validateLogoutRequest(
$logout
) ) {
return
$self
->p->sendError(
$req
,
"SLO request is not valid"
, 400 );
}
if
(
$relaystate
) {
$logout
->msg_relayState(
$relaystate
);
$self
->logger->debug(
"Set $relaystate in RelayState"
);
}
my
$sloInfos
;
$sloInfos
->{type} =
'sloStatus'
;
$sloInfos
->{_utime} =
time
;
$sloInfos
->{_logout} =
$logout
->
dump
;
$sloInfos
->{_session} =
$logout
->get_session() ?
$logout
->get_session()->
dump
:
""
;
$sloInfos
->{_method} =
$method
;
my
$sloStatusSessionInfo
=
$self
->getSamlSession(
undef
,
$sloInfos
);
my
$relayID
=
$sloStatusSessionInfo
->id;
$self
->logger->debug(
"Create relay session $relayID"
);
my
$provider_nb
=
$self
->sendLogoutRequestToProviders(
$req
,
$logout
,
$relayID
);
unless
(
$self
->deleteSAMLSecondarySessions(
$local_session_id
) ) {
$self
->logger->error(
"Fail to delete SAML sessions"
);
}
$req
->data->{samlSLOCalled} = 1;
my
$doAuthLogout
= 0;
my
$logoutContextSession
;
if
(
$req
->sessionInfo->{_lassoSessionDump} ) {
$doAuthLogout
= 1;
my
$logoutInfos
= {
logout
=>
$logout
->
dump
,
spConfKey
=>
$spConfKey
,
method
=>
$method
,
provider_nb
=>
$provider_nb
,
relayID
=>
$relayID
,
_utime
=>
time
(),
};
$logoutContextSession
=
$self
->getSamlSession(
undef
,
$logoutInfos
);
my
$uri
=
URI->new(
$self
->p->buildUrl(
$req
->portal,
'saml'
,
'singleLogoutResume'
) );
$uri
->query_param(
ResumeParams
=>
$logoutContextSession
->id );
$req
->{issuerUrldc} =
$uri
->as_string;
}
my
$savedInfo
=
$req
->info;
$req
->setInfo(
''
);
my
$tmp
=
$req
->data->{nofail};
$req
->data->{nofail} = 1;
$req
->steps( [
@{
$self
->p->beforeLogout },
(
$doAuthLogout
?
'authLogout'
: () ),
'deleteSession'
]
);
my
$res
=
$self
->p->process(
$req
);
$req
->data->{nofail} =
$tmp
;
if
(
$res
eq PE_REDIRECT ) {
if
(
$savedInfo
) {
$logoutContextSession
->update( {
info
=>
$savedInfo
} );
}
return
$self
->p->
do
(
$req
, [
sub
{ PE_REDIRECT } ] );
}
$req
->info(
$savedInfo
);
return
$self
->_finishSlo(
$req
,
$logout
,
$method
,
$spConfKey
,
$provider_nb
,
$relayID
);
}
elsif
(
$response
) {
my
$result
=
$self
->processLogoutResponseMsg(
$logout
,
$response
);
unless
(
$result
) {
$self
->logger->error(
"Fail to process logout response"
);
$self
->imgnok(
$req
);
}
$self
->logger->debug(
"Logout response is valid"
);
my
$h
=
$self
->p->processHook(
$req
,
'samlGotLogoutResponse'
,
$logout
);
$self
->imgnok(
$req
)
if
(
$h
!= PE_OK );
$self
->imgnok(
$req
)
unless
(
$self
->checkDestination(
$logout
->response,
$url
) );
my
$sp
=
$logout
->remote_providerID();
$self
->logger->debug(
"Found entityID $sp in SAML message"
);
my
$spConfKey
=
$self
->spList->{
$sp
}->{confKey};
unless
(
$spConfKey
) {
$self
->logger->error(
"$sp do not match any SP in configuration"
);
$self
->imgnok(
$req
);
}
$self
->logger->debug(
"$sp match $spConfKey SP in configuration"
);
my
$checkSLOMessageSignature
=
$self
->spOptions->{
$sp
}
->{samlSPMetaDataOptionsCheckSLOMessageSignature};
if
(
$checkSLOMessageSignature
) {
unless
(
$self
->checkSignatureStatus(
$logout
) ) {
$self
->logger->error(
"Could not verify signature of"
.
" incoming SLO request from $spConfKey"
);
$self
->imgnok(
$req
);
}
else
{
$self
->logger->debug(
"Signature is valid"
);
}
}
else
{
$self
->logger->debug(
"Message signature will not be checked"
);
}
if
(
$relaystate
) {
my
$sloStatusSessionInfos
=
$self
->getSamlSession(
$relaystate
);
if
(
$sloStatusSessionInfos
) {
$sloStatusSessionInfos
->update( {
$spConfKey
=> 1 } );
$self
->logger->debug(
"Store SLO status for $spConfKey in session $relaystate"
);
}
else
{
$self
->logger->
warn
(
"Unable to store SLO status for"
.
" $spConfKey in session $relaystate"
);
}
}
else
{
$self
->logger->
warn
(
"Unable to store SLO status for"
.
" $spConfKey because there is no RelayState"
);
}
$self
->logger->debug(
"Display OK status for SLO on $spConfKey"
);
$self
->imgok(
$req
);
}
else
{
return
$self
->p->sendError(
$req
,
"No request or response found"
, 400 );
}
}
sub
attributeServer {
my
(
$self
,
$req
, ) =
@_
;
my
$url
=
$req
->uri;
$self
->logger->debug(
"URL $url detected as an attribute service URL"
);
my
$att_request
=
$req
->content;
my
$att_response
;
my
$query
=
$self
->processAttributeRequest(
$self
->lassoServer,
$att_request
);
unless
(
$query
) {
return
$self
->p->sendError(
$req
,
"Unable to process attribute request"
, 400 );
}
my
$sp
=
$query
->remote_providerID();
$self
->logger->debug(
"Found entityID $sp in SAML message"
);
my
$spConfKey
=
$self
->spList->{
$sp
}->{confKey};
unless
(
$spConfKey
) {
return
$self
->p->sendError(
$req
,
"$sp do not match any SP in configuration"
, 400 );
}
$self
->logger->debug(
"$sp match $spConfKey SP in configuration"
);
unless
(
$self
->checkDestination(
$query
->request,
$url
) ) {
return
$self
->p->sendError(
$req
,
"Bad destination $url"
, 400 );
}
unless
(
$self
->validateAttributeRequest(
$query
) ) {
return
$self
->p->sendError(
$req
,
"Attribute request not valid"
, 400 );
}
my
$name_id
=
$query
->nameIdentifier();
unless
(
$name_id
) {
$self
->p->sendError(
$req
,
"Fail to get NameID from attribute request"
, 400 );
}
my
$user
=
$name_id
->content();
my
$sessionInfo
;
my
$saml_sessions
=
Lemonldap::NG::Common::Apache::Session->searchOn(
$self
->amOpts,
"_nameID"
,
$name_id
->
dump
);
if
(
my
@saml_sessions_keys
=
grep
{
$saml_sessions
->{
$_
}->{_session_kind} eq
$self
->sessionKind }
keys
%$saml_sessions
)
{
if
(
$#saml_sessions_keys
> 0 ) {
$self
->logger->
warn
(
"More than one SAML session found for user $user"
);
}
my
$saml_session
=
shift
@saml_sessions_keys
;
$self
->logger->debug(
"Retrieve SAML session $saml_session for user $user"
);
my
$samlSessionInfo
=
$self
->getSamlSession(
$saml_session
);
my
$real_session
=
$samlSessionInfo
->data->{_saml_id};
$self
->logger->debug(
"Retrieve real session $real_session for user $user"
);
$sessionInfo
=
$self
->p->getApacheSession(
$real_session
);
unless
(
$sessionInfo
) {
return
$self
->p->sendError(
$req
,
"Cannot get session $real_session"
, 500 );
}
}
else
{
return
$self
->p->sendError(
$req
,
"No SAML session found for user $user"
, 400 );
}
my
@requested_attributes
;
eval
{
@requested_attributes
=
$query
->request()->Attribute(); };
if
($@) {
$self
->checkLassoError($@);
return
$self
->p->sendError(
$req
,
"Unable to get requested attributes"
, 400 );
}
my
@returned_attributes
;
foreach
(
keys
%{
$self
->spAttributes->{
$sp
} } ) {
my
$sp_attr
=
$_
;
my
(
$mandatory
,
$name
,
$format
,
$friendly_name
) =
split
( /;/,
$self
->spAttributes->{
$sp
}->{
$sp_attr
} );
foreach
(
@requested_attributes
) {
my
$req_attr
=
$_
;
my
$rname
=
$req_attr
->Name();
my
$rformat
=
$req_attr
->NameFormat();
my
$rfriendly_name
=
$req_attr
->FriendlyName();
next
unless
(
$rname
=~ /^
$name
$/ );
next
if
(
$rformat
and
$rformat
!~ /^
$format
$/ );
next
if
(
$rfriendly_name
and
$rfriendly_name
!~ /^
$friendly_name
$/ );
$self
->logger->debug(
"SP $spConfKey is authorized to access attribute $rname"
);
$self
->logger->debug(
"Attribute $rname is linked to $sp_attr session key"
);
my
$rvalue
=
$self
->getAttributeValue(
$rname
,
$rformat
,
$rfriendly_name
,
[
$req_attr
] );
$self
->logger->debug(
"Some values are explicitly requested: $rvalue"
)
if
defined
$rvalue
;
if
(
$sessionInfo
->data->{
$sp_attr
} ) {
my
@values
=
split
$self
->conf->{multiValuesSeparator},
$sessionInfo
->data->{
$sp_attr
};
my
@saml2values
;
my
$ret_attr
=
$self
->createAttribute(
$rname
,
$rformat
,
$rfriendly_name
);
unless
(
$ret_attr
) {
return
$self
->p->sendError(
$req
,
"Unable to create a new SAML attribute"
, 500 );
}
foreach
(
@values
) {
my
$local_value
=
$_
;
if
(
$rvalue
and !
map
( /^
$local_value
$/,
split
(
$self
->conf->{multiValuesSeparator},
$rvalue
) )
)
{
$self
->logger->
warn
(
"$local_value value is not in"
.
" requested values, it will not be sent"
);
next
;
}
my
$saml2value
=
$self
->createAttributeValue(
$local_value
,
$self
->spOptions->{
$sp
}
->{samlSPMetaDataOptionsForceUTF8} );
unless
(
$saml2value
) {
return
$self
->p->sendError(
$req
,
"Unable to create a new SAML attribute value"
,
400 );
}
push
@saml2values
,
$saml2value
;
$self
->logger->debug(
"Push $local_value in SAML attribute $name"
);
}
$ret_attr
->AttributeValue(
@saml2values
);
push
@returned_attributes
,
$ret_attr
;
}
else
{
$self
->logger->debug(
"No session value for $sp_attr"
);
}
}
}
if
(
scalar
@returned_attributes
) {
my
$attribute_statement
;
eval
{
$attribute_statement
= Lasso::Saml2AttributeStatement->new(); };
if
($@) {
$self
->checkLassoError($@);
return
$self
->p->sendError(
$req
,
'An error occurs, see IdP logs'
,
500 );
}
$attribute_statement
->Attribute(
@returned_attributes
);
my
$assertion
;
eval
{
$assertion
= Lasso::Saml2Assertion->new(); };
if
($@) {
$self
->checkLassoError($@);
return
$self
->p->sendError(
$req
,
'An error occurs, see IdP logs'
,
500 );
}
my
@attributes_statement
= (
$attribute_statement
);
$assertion
->AttributeStatement(
@attributes_statement
);
$query
->response->Assertion( (
$assertion
) );
}
$att_response
=
$self
->buildAttributeResponse(
$query
);
unless
(
$att_response
) {
$self
->p->sendError(
$req
,
"Unable to build attribute response"
, 500 );
}
return
[
200,
[
'Content-Type'
=>
'text/xml'
,
'Content-Length'
=>
length
(
$att_response
)
],
[
$att_response
]
];
}
sub
imgok {
my
$self
=
shift
;
return
$self
->p->imgok(
@_
);
}
sub
imgnok {
my
$self
=
shift
;
return
$self
->p->imgnok(
@_
);
}
sub
sendImage {
my
$self
=
shift
;
return
$self
->p->sendImage(
@_
);
}
sub
normalize_url {
my
(
$self
,
$url
,
$samlPath
,
$metadataUrl
) =
@_
;
my
$initialPath
=
""
;
my
$finalPath
=
""
;
if
(
$url
=~ m/(
$samlPath
)/ ) {
$initialPath
= $1;
}
if
(
$metadataUrl
=~ m/(
$samlPath
)/ ) {
$finalPath
= $1;
}
if
(
$initialPath
ne
""
and
$finalPath
ne
""
and
$initialPath
ne
$finalPath
)
{
$self
->logger->debug(
"Normalizing url path form $initialPath to $finalPath"
);
$url
=~ s/
$initialPath
/
$finalPath
/;
}
return
$url
;
}
1;