PE_OK
PE_REDIRECT
PE_LOGOUT_OK
PE_SLO_ERROR
)
;
our
$VERSION
=
'2.21.0'
;
has
lassoServer
=> (
is
=>
'rw'
);
has
idpList
=> (
is
=>
'rw'
,
default
=>
sub
{ {} } );
has
idpRules
=> (
is
=>
'rw'
,
default
=>
sub
{ {} } );
has
idpAttributes
=> (
is
=>
'rw'
,
default
=>
sub
{ {} } );
has
idpOptions
=> (
is
=>
'rw'
,
default
=>
sub
{ {} } );
has
spList
=> (
is
=>
'rw'
,
default
=>
sub
{ {} } );
has
spRules
=> (
is
=>
'rw'
,
default
=>
sub
{ {} } );
has
spLevelRules
=> (
is
=>
'rw'
,
default
=>
sub
{ {} } );
has
spMacros
=> (
is
=>
'rw'
,
default
=>
sub
{ {} } );
has
spAttributes
=> (
is
=>
'rw'
,
default
=>
sub
{ {} } );
has
spOptions
=> (
is
=>
'rw'
,
default
=>
sub
{ {} } );
has
parser
=> (
is
=>
'rw'
,
builder
=>
sub
{
return
XML::LibXML->new(
load_ext_dtd
=> 0,
expand_entities
=> 0 );
}
);
has
ua
=> (
is
=>
'rw'
,
lazy
=> 1,
builder
=>
sub
{
my
$ua
= Lemonldap::NG::Common::UserAgent->new(
$_
[0]->{conf} );
$ua
->env_proxy();
return
$ua
;
}
);
has
aModule
=> (
is
=>
'rw'
);
has
amOpts
=> (
is
=>
'rw'
);
BEGIN {
eval
'use Glib;'
;
if
($@) {
print
STDERR
"Glib Lasso messages will not be catched (require Glib module)\n"
;
eval
"use constant GLIB => 0"
;
}
else
{
eval
"use constant GLIB => 1"
;
}
eval
'use Lasso;'
;
if
($@) {
print
STDERR
"Lasso.pm not loaded: $@"
;
eval
'use constant LASSO => 0;use constant BADLASSO => 0;use constant LASSOTHINSESSIONS => 0'
;
}
else
{
no
strict
'subs'
;
eval
'use constant LASSO => 1'
;
my
$lasso_check_version_mode
=
eval
'Lasso::Constants::CHECK_VERSION_NUMERIC'
;
my
$check_version
=
Lasso::check_version( 2, 3, 0,
$lasso_check_version_mode
);
unless
(
$check_version
) {
eval
'use constant BADLASSO => 1'
;
}
else
{
eval
'use constant BADLASSO => 0'
;
}
eval
'Lasso::set_flag("thin-sessions");'
;
if
($@) {
eval
'use constant LASSOTHINSESSIONS => 0'
;
}
else
{
eval
'use constant LASSOTHINSESSIONS => 1'
;
}
}
}
sub
init {
my
(
$self
) =
@_
;
my
$moduleOptions
;
if
(
$self
->conf->{samlStorage} ) {
$moduleOptions
=
$self
->conf->{samlStorageOptions} || {};
$moduleOptions
->{backend} =
$self
->conf->{samlStorage};
}
else
{
$moduleOptions
=
$self
->conf->{globalStorageOptions} || {};
$moduleOptions
->{backend} =
$self
->conf->{globalStorage};
}
$self
->aModule(
$moduleOptions
->{backend} );
$self
->amOpts(
$moduleOptions
);
unless
(LASSO) {
$self
->logger->error(
"Module Lasso not loaded (see below)"
);
return
0;
}
if
(BADLASSO) {
$self
->logger->error(
'Lasso version >= 2.3.0 required'
);
return
0;
}
unless
(LASSOTHINSESSIONS) {
$self
->logger->
warn
(
'Lasso thin-sessions flag could not be set'
);
}
else
{
$self
->logger->debug(
'Lasso thin-sessions flag set'
);
}
if
(GLIB) {
Glib::Log->set_handler(
"Lasso"
,
[
qw/ error critical warning message info debug /
],
sub
{
$self
->logger->debug(
$_
[0] .
" error "
.
$_
[1] .
": "
.
$_
[2] );
}
);
}
return
0
unless
(
$self
->lassoServer(
$self
->loadService ) );
$self
->addUnauthRoute(
(
$self
->{path} ||
'saml'
) =>
{
'metadata'
=> {
':type'
=>
'metadata'
} },
[
'GET'
]
);
$self
->addAuthRoute(
(
$self
->{path} ||
'saml'
) =>
{
'metadata'
=> {
':type'
=>
'metadata'
} },
[
'GET'
]
);
return
1;
}
sub
loadService {
my
(
$self
) =
@_
;
unless
(
$self
->conf->{samlServicePublicKeySig}
and
$self
->conf->{samlServicePrivateKeySig} )
{
$self
->logger->error(
'SAML private and public key not found in configuration'
);
return
0;
}
my
$serviceCertificate
;
if
(
$self
->conf->{samlServiceUseCertificateInResponse}
and
$self
->conf->{samlServicePublicKeySig} =~ /CERTIFICATE/ )
{
$serviceCertificate
=
$self
->conf->{samlServicePublicKeySig};
$self
->logger->debug(
'Certificate will be used in SAML responses'
);
}
$self
->logger->debug(
"Get Metadata for this service"
);
my
$service_metadata
= Lemonldap::NG::Common::Conf::SAML::Metadata->new();
my
$server
=
$self
->createServer(
$service_metadata
->serviceToXML( {
%{
$self
->conf },
portal
=>
$self
->p->HANDLER->tsv->{portal}->()
},
''
),
$self
->conf->{samlServicePrivateKeySig},
$self
->conf->{samlServicePrivateKeySigPwd},
(
$self
->conf->{samlServicePrivateKeyEnc}
? (
$self
->conf->{samlServicePrivateKeyEnc},
$self
->conf->{samlServicePrivateKeyEncPwd}
)
: (
$self
->conf->{samlServicePrivateKeySig},
$self
->conf->{samlServicePrivateKeySigPwd}
)
),
$serviceCertificate
);
unless
(
$server
) {
$self
->logger->error(
'Unable to create Lasso server'
);
return
0;
}
my
$method
=
$self
->conf->{samlServiceSignatureMethod} ||
'RSA_SHA1'
;
$server
->signature_method(
$self
->getSignatureMethod(
$method
) );
$self
->logger->debug(
"Set $method as SAML server signature method "
);
$self
->logger->debug(
"Service created"
);
return
$server
;
}
sub
loadIDPs {
my
(
$self
) =
@_
;
foreach
(
keys
%{
$self
->conf->{samlIDPMetaDataXML} } ) {
$self
->loadIDP(
$_
);
}
return
1;
}
sub
loadIDP {
my
(
$self
,
$idpConfKey
) =
@_
;
$self
->logger->debug(
"Get Metadata for IDP $idpConfKey"
);
my
$idp_metadata
=
$self
->conf->{samlIDPMetaDataXML}->{
$idpConfKey
}->{samlIDPMetaDataXML};
if
(
$idp_metadata
) {
if
(
$self
->load_idp_from_conf(
$idpConfKey
,
$idp_metadata
) ) {
$self
->logger->debug(
"IDP $idpConfKey added"
);
}
else
{
$self
->logger->
warn
(
"Ignoring IDP $idpConfKey"
);
}
}
}
sub
load_idp_from_conf {
my
(
$self
,
$idpConfKey
,
$idp_metadata
) =
@_
;
if
(
ref
$idp_metadata
eq
"HASH"
) {
$self
->logger->error(
"Metadata for IDP $idpConfKey is in old format. Please reload them from Manager"
);
return
;
}
if
(
$self
->conf->{samlMetadataForceUTF8} ) {
$idp_metadata
= encode(
"utf8"
,
$idp_metadata
);
}
my
(
$tmp
,
$entityID
) = (
$idp_metadata
=~ /entityID=(['"])(.+?)\1/si );
decode_entities(
$entityID
);
return
$self
->load_idp_metadata(
$idpConfKey
,
$idp_metadata
,
$entityID
,
$self
->conf->{samlIDPMetaDataExportedAttributes}->{
$idpConfKey
},
$self
->conf->{samlIDPMetaDataOptions}->{
$idpConfKey
},
);
}
sub
load_idp_metadata {
my
(
$self
,
$idpConfKey
,
$idp_metadata
,
$entityID
,
$attributes
,
$options
)
=
@_
;
my
$result
=
$self
->addIDP(
$self
->lassoServer,
$idp_metadata
);
unless
(
$result
) {
$self
->logger->error(
"Fail to use IDP $idpConfKey Metadata"
);
return
;
}
my
$name
=
$self
->getOrganizationName(
$self
->lassoServer,
$entityID
)
||
ucfirst
(
$idpConfKey
);
$self
->idpList->{
$entityID
}->{confKey} =
$idpConfKey
;
$self
->idpList->{
$entityID
}->{name} =
$name
;
my
$encryption_mode
=
$options
->{samlIDPMetaDataOptionsEncryptionMode} ||
"none"
;
my
$lasso_encryption_mode
=
$self
->getEncryptionMode(
$encryption_mode
);
unless
(
$self
->setProviderEncryptionMode(
$self
->lassoServer->get_provider(
$entityID
),
$lasso_encryption_mode
)
)
{
$self
->logger->error(
"Unable to set encryption mode $encryption_mode on IDP $idpConfKey"
);
delete
$self
->idpList->{
$entityID
};
return
;
}
$self
->logger->debug(
"Set encryption mode $encryption_mode on IDP $idpConfKey"
);
my
$signature_method
=
$options
->{samlIDPMetaDataOptionsSignatureMethod};
if
(
$signature_method
) {
my
$lasso_signature_method
=
$self
->getSignatureMethod(
$signature_method
);
unless
(
$self
->setProviderSignatureMethod(
$self
->lassoServer->get_provider(
$entityID
),
$lasso_signature_method
)
)
{
$self
->logger->error(
"Unable to set signature method $signature_method on IDP $idpConfKey"
);
delete
$self
->idpList->{
$entityID
};
return
;
}
$self
->logger->debug(
"Set signature method $signature_method on IDP $idpConfKey"
);
}
$self
->idpList->{
$entityID
}->{displayName} =
$options
->{samlIDPMetaDataOptionsDisplayName};
$self
->idpList->{
$entityID
}->{icon} =
$options
->{samlIDPMetaDataOptionsIcon};
$self
->idpList->{
$entityID
}->{tooltip} =
$options
->{samlIDPMetaDataOptionsTooltip};
$self
->idpList->{
$entityID
}->{order} =
$options
->{samlIDPMetaDataOptionsSortNumber};
my
$cond
=
$options
->{samlIDPMetaDataOptionsResolutionRule};
if
(
length
$cond
) {
$cond
=
$self
->p->HANDLER->substitute(
$cond
);
unless
(
$cond
=
$self
->p->HANDLER->buildSub(
$cond
) ) {
$self
->logger->error(
'SAML IdP rule error: '
.
$self
->p->HANDLER->tsv->{jail}->error );
delete
$self
->idpList->{
$entityID
};
return
;
}
$self
->idpRules->{
$entityID
} =
$cond
;
}
$self
->idpAttributes->{
$entityID
} = { %{
$attributes
|| {} } };
$self
->idpOptions->{
$entityID
} = { %{
$options
|| {} } };
return
1;
}
sub
loadSPs {
my
(
$self
) =
@_
;
foreach
(
keys
%{
$self
->conf->{samlSPMetaDataXML} } ) {
$self
->loadSP(
$_
);
}
return
1;
}
sub
loadSP {
my
(
$self
,
$spConfKey
) =
@_
;
$self
->logger->debug(
"Get Metadata for SP $spConfKey"
);
my
$sp_metadata
=
$self
->conf->{samlSPMetaDataXML}->{
$spConfKey
}->{samlSPMetaDataXML};
if
(
$sp_metadata
) {
$self
->load_sp_from_conf(
$spConfKey
,
$sp_metadata
);
$self
->logger->debug(
"SP $spConfKey added"
);
}
}
sub
load_sp_from_conf {
my
(
$self
,
$spConfKey
,
$sp_metadata
) =
@_
;
if
(
ref
$sp_metadata
eq
"HASH"
) {
$self
->logger->error(
"Metadata for SP $spConfKey is in old format. Please reload them from Manager"
);
return
;
}
if
(
$self
->conf->{samlMetadataForceUTF8} ) {
$sp_metadata
= encode(
"utf8"
,
$sp_metadata
);
}
my
(
$tmp
,
$entityID
) = (
$sp_metadata
=~ /entityID=(['"])(.+?)\1/si );
decode_entities(
$entityID
);
return
$self
->load_sp_metadata(
$spConfKey
,
$sp_metadata
,
$entityID
,
$self
->conf->{samlSPMetaDataExportedAttributes}->{
$spConfKey
},
$self
->conf->{samlSPMetaDataOptions}->{
$spConfKey
},
$self
->conf->{samlSPMetaDataMacros}->{
$spConfKey
},
);
}
sub
load_sp_metadata {
my
(
$self
,
$spConfKey
,
$sp_metadata
,
$entityID
,
$attributes
,
$options
,
$macros
)
=
@_
;
my
$valid
= 1;
my
$rule
=
$options
->{samlSPMetaDataOptionsRule};
if
(
length
$rule
) {
$rule
=
$self
->p->buildRule(
$rule
,
"access rule for SP $spConfKey"
);
unless
(
$rule
) {
$valid
= 0;
}
}
my
$levelrule
=
$options
->{samlSPMetaDataOptionsAuthnLevel} || 0;
$levelrule
=
$self
->p->buildRule(
$levelrule
,
"required authentication level rule for SP $spConfKey"
);
unless
(
$levelrule
) {
$valid
= 0;
}
my
$compiledMacros
= {};
for
my
$macroAttr
(
keys
%{
$macros
} ) {
my
$macroRule
=
$macros
->{
$macroAttr
};
if
(
length
$macroRule
) {
$macroRule
=
$self
->p->HANDLER->substitute(
$macroRule
);
if
(
$macroRule
=
$self
->p->HANDLER->buildSub(
$macroRule
) ) {
$compiledMacros
->{
$macroAttr
} =
$macroRule
;
}
else
{
$valid
= 0;
$self
->logger->error(
"Error processing macro $macroAttr for SAML SP $spConfKey"
.
$self
->p->HANDLER->tsv->{jail}->error );
}
}
}
if
(
$valid
) {
$self
->spRules->{
$spConfKey
} =
$rule
;
$self
->spLevelRules->{
$spConfKey
} =
$levelrule
;
$self
->spMacros->{
$spConfKey
} =
$compiledMacros
;
}
else
{
$self
->logger->error(
"SAML SP $spConfKey has errors and will be ignored"
);
return
;
}
$self
->spAttributes->{
$entityID
} = { %{
$attributes
|| {} } };
$self
->spOptions->{
$entityID
} = { %{
$options
|| {} } };
my
$result
=
$self
->addSP(
$self
->lassoServer,
$sp_metadata
);
unless
(
$result
) {
$self
->logger->error(
"Fail to use SP $spConfKey Metadata"
);
return
;
}
my
$name
=
$self
->getOrganizationName(
$self
->lassoServer,
$entityID
)
||
ucfirst
(
$spConfKey
);
$self
->spList->{
$entityID
}->{confKey} =
$spConfKey
;
$self
->spList->{
$entityID
}->{name} =
$name
;
my
$encryption_mode
=
$options
->{samlSPMetaDataOptionsEncryptionMode} ||
"none"
;
my
$lasso_encryption_mode
=
$self
->getEncryptionMode(
$encryption_mode
);
unless
(
$self
->setProviderEncryptionMode(
$self
->lassoServer->get_provider(
$entityID
),
$lasso_encryption_mode
)
)
{
$self
->logger->error(
"Unable to set encryption mode $encryption_mode on SP $spConfKey"
);
return
;
}
$self
->logger->debug(
"Set encryption mode $encryption_mode on SP $spConfKey"
);
my
$signature_method
=
$options
->{samlSPMetaDataOptionsSignatureMethod};
if
(
$signature_method
) {
my
$lasso_signature_method
=
$self
->getSignatureMethod(
$signature_method
);
unless
(
$self
->setProviderSignatureMethod(
$self
->lassoServer->get_provider(
$entityID
),
$lasso_signature_method
)
)
{
$self
->logger->error(
"Unable to set signature method $signature_method on SP $spConfKey"
);
return
;
}
$self
->logger->debug(
"Set signature method $signature_method on SP $spConfKey"
);
}
}
sub
lazy_load_message {
my
(
$self
,
$message
) =
@_
;
return
unless
$message
;
my
$entityID
=
$self
->getIssuer(
$message
);
if
(
$entityID
) {
$self
->logger->debug(
"Found entityID $entityID in message"
);
return
$self
->lazy_load_entityid(
$entityID
);
}
}
sub
lazy_load_entityid {
my
(
$self
,
$entityID
) =
@_
;
return
unless
$entityID
;
my
$provider
=
$self
->lassoServer->get_provider(
$entityID
);
unless
(
$provider
) {
$self
->logger->debug(
"Lazy loading provider $entityID"
);
return
$self
->lazy_load_config(
$entityID
);
}
}
sub
load_config {
my
(
$self
,
$entityID
) =
@_
;
my
$config
= {};
$self
->p->processHook( {},
'getSamlConfig'
,
$entityID
,
$config
);
my
$info
;
if
(
$config
->{sp_metadata} ) {
$info
->{sp_confKey} =
$config
->{sp_confKey};
$self
->load_sp_metadata(
$config
->{sp_confKey},
$config
->{sp_metadata},
$entityID
,
$config
->{sp_attributes},
$config
->{sp_options},
$config
->{sp_macros},
);
}
if
(
$config
->{idp_metadata} ) {
$info
->{idp_confKey} =
$config
->{idp_confKey};
if
(
$self
->load_idp_metadata(
$config
->{idp_confKey},
$config
->{idp_metadata},
$entityID
,
$config
->{idp_attributes},
$config
->{idp_options}
)
)
{
$self
->logger->debug(
"IDP $config->{idp_confKey} added from hook"
);
}
else
{
$self
->logger->
warn
(
"Ignoring IDP $config->{idp_confKey}"
);
}
}
my
$ttl
=
$config
->{ttl};
return
{ (
$ttl
? (
ttl
=>
$ttl
) : () ),
(
$info
? (
info
=>
$info
) : () ) };
}
sub
checkMessage {
my
(
$self
,
$req
,
$url
,
$request_method
,
$content_type
,
$profile_type
) =
@_
;
$profile_type
||=
"login"
;
my
$profile
;
$profile
=
$self
->createLogin(
$self
->lassoServer )
if
(
$profile_type
eq
"login"
);
$profile
=
$self
->createLogout(
$self
->lassoServer )
if
(
$profile_type
eq
"logout"
);
my
$relaystate
=
$req
->param(
'RelayState'
);
my
(
$method
,
$request
,
$response
,
$artifact
,
$message
);
if
(
$request_method
eq
'GET'
) {
$method
= Lasso::Constants::HTTP_METHOD_REDIRECT;
$self
->logger->debug(
"SAML method: HTTP-REDIRECT"
);
if
(
$req
->param(
'SAMLResponse'
) ) {
$response
=
$self
->getQueryString(
$req
);
$self
->logger->debug(
"HTTP-REDIRECT: SAML Response $response"
);
}
if
(
$req
->param(
'SAMLRequest'
) ) {
$request
=
$self
->getQueryString(
$req
);
$self
->logger->debug(
"HTTP-REDIRECT: SAML Request $request"
);
}
if
(
$req
->param(
'SAMLart'
) ) {
$artifact
=
$self
->getQueryString(
$req
);
$self
->logger->debug(
"HTTP-REDIRECT: SAML Artifact $artifact"
);
$method
= Lasso::Constants::HTTP_METHOD_ARTIFACT_GET;
$message
=
$self
->resolveArtifact(
$profile
,
$artifact
,
$method
);
if
(
$self
->_isArtifactSamlResponse(
$message
) ) {
$response
=
$message
;
}
else
{
$request
=
$message
;
}
}
}
elsif
(
$request_method
=~ /^POST$/ ) {
if
(
$content_type
!~ /xml/ ) {
$method
= Lasso::Constants::HTTP_METHOD_POST;
$self
->logger->debug(
"SAML method: HTTP-POST"
);
if
(
$req
->param(
'SAMLResponse'
) ) {
$response
=
$req
->param(
'SAMLResponse'
);
$response
=~ s/ /+/gs;
$self
->logger->debug(
"HTTP-POST: SAML Response $response"
);
}
elsif
(
$req
->param(
'SAMLRequest'
) ) {
$request
=
$req
->param(
'SAMLRequest'
);
$self
->logger->debug(
"HTTP-POST: SAML Request $request"
);
}
elsif
(
$req
->param(
'SAMLart'
) ) {
$artifact
=
$req
->param(
'SAMLart'
);
$self
->logger->debug(
"HTTP-POST: SAML Artifact $artifact"
);
$method
= Lasso::Constants::HTTP_METHOD_ARTIFACT_POST;
$message
=
$self
->resolveArtifact(
$profile
,
$artifact
,
$method
);
if
(
$self
->_isArtifactSamlResponse(
$message
) ) {
$response
=
$message
;
}
else
{
$request
=
$message
;
}
}
}
else
{
$method
= Lasso::Constants::HTTP_METHOD_SOAP;
$self
->logger->debug(
"SAML method: HTTP-SOAP"
);
$request
=
$req
->content;
$self
->logger->debug(
"HTTP-SOAP: SAML Request $request"
);
}
}
return
(
$request
,
$response
,
$method
,
$relaystate
,
$artifact
? 1 : 0 );
}
sub
_isArtifactSamlResponse {
my
(
$self
,
$message
) =
@_
;
my
$type
=
eval
{
my
$resp
= Lasso::Samlp2ArtifactResponse->new;
$resp
->init_from_message(
$message
);
$resp
->any->get_name;
};
if
($@) {
$self
->logger->
warn
(
"Could not detect type of Artifact response"
);
return
;
}
$self
->logger->debug(
"Artifact response type is $type"
);
if
(
$type
eq
"Response"
) {
return
1;
}
else
{
return
0;
}
}
sub
checkLassoError {
my
(
$self
,
$error
,
$level
) =
@_
;
my
(
$lasso_result
,
$lasso_error_code
) =
$self
->getLassoError(
$error
,
$level
);
return
$lasso_result
;
}
sub
getLassoError {
my
(
$self
,
$error
,
$level
) =
@_
;
$level
||=
'error'
;
unless
(
ref
(
$error
) and
$error
->isa(
"Lasso::Error"
) ) {
return
( 1,
undef
)
unless
$error
;
$self
->p->lmLog(
"Lasso error: $error"
,
$level
);
return
( 0,
undef
);
}
if
(
$error
->{code} ) {
$self
->p->lmLog(
"Lasso error code "
.
$error
->{code} .
": "
.
$error
->{message},
$level
);
return
( 0,
$error
->{code} );
}
return
( 1,
undef
);
}
sub
createServer {
my
(
$self
,
$metadata
,
$private_key
,
$private_key_password
,
$private_key_enc
,
$private_key_enc_password
,
$certificate
)
=
@_
;
my
$server
;
my
$save_env
=
$ENV
{
'SSL_CERT_FILE'
};
$ENV
{
'SSL_CERT_FILE'
} =
"/dev/null"
;
eval
{
$server
= Lasso::Server::new_from_buffers(
$metadata
,
$private_key
,
$private_key_password
,
$certificate
);
if
(
$server
&&
$private_key_enc
) {
Lasso::Server::set_encryption_private_key_with_password(
$server
,
$private_key_enc
,
$private_key_enc_password
);
}
};
if
(
defined
$save_env
) {
$ENV
{
'SSL_CERT_FILE'
} =
$save_env
;
}
else
{
delete
$ENV
{
'SSL_CERT_FILE'
};
}
if
($@) {
$self
->checkLassoError($@);
return
;
}
return
$server
;
}
sub
addIDP {
my
(
$self
,
$server
,
$metadata
,
$public_key
,
$ca_cert_chain
) =
@_
;
return
0
unless
(
$server
->isa(
"Lasso::Server"
) and
defined
$metadata
);
return
$self
->addProvider(
$server
, Lasso::Constants::PROVIDER_ROLE_IDP,
$metadata
,
$public_key
,
$ca_cert_chain
);
}
sub
addSP {
my
(
$self
,
$server
,
$metadata
,
$public_key
,
$ca_cert_chain
) =
@_
;
return
0
unless
(
$server
->isa(
"Lasso::Server"
) and
defined
$metadata
);
return
$self
->addProvider(
$server
, Lasso::Constants::PROVIDER_ROLE_SP,
$metadata
,
$public_key
,
$ca_cert_chain
);
}
sub
addAA {
my
(
$self
,
$server
,
$metadata
,
$public_key
,
$ca_cert_chain
) =
@_
;
return
0
unless
(
$server
->isa(
"Lasso::Server"
) and
defined
$metadata
);
return
$self
->addProvider(
$server
,
Lasso::Constants::PROVIDER_ROLE_ATTRIBUTE_AUTHORITY,
$metadata
,
$public_key
,
$ca_cert_chain
);
}
sub
addProvider {
my
(
$self
,
$server
,
$role
,
$metadata
,
$public_key
,
$ca_cert_chain
) =
@_
;
return
0
unless
(
$server
->isa(
"Lasso::Server"
)
and
defined
$role
and
defined
$metadata
);
my
$save_env
=
$ENV
{
'SSL_CERT_FILE'
};
$ENV
{
'SSL_CERT_FILE'
} =
"/dev/null"
;
eval
{
Lasso::Server::add_provider_from_buffer(
$server
,
$role
,
$metadata
,
$public_key
,
$ca_cert_chain
);
};
if
(
defined
$save_env
) {
$ENV
{
'SSL_CERT_FILE'
} =
$save_env
;
}
else
{
delete
$ENV
{
'SSL_CERT_FILE'
};
}
return
$self
->checkLassoError($@);
}
sub
getOrganizationName {
my
(
$self
,
$server
,
$idp
) =
@_
;
my
(
$provider
,
$node
);
eval
{
$provider
= Lasso::Server::get_provider(
$server
,
$idp
); };
if
($@) {
$self
->checkLassoError($@);
return
;
}
eval
{
$node
= Lasso::Provider::get_organization(
$provider
); };
if
($@) {
$self
->checkLassoError($@);
return
;
}
return
unless
$node
;
my
$data
=
eval
{
$self
->parser->parse_string(
$node
)->documentElement };
if
(
my
$exception
= $@ ) {
$self
->logger->
warn
(
"Could not parse Organization for $idp: $@"
);
}
return
unless
$data
;
return
$data
->getElementsByTagNameNS(
"urn:oasis:names:tc:SAML:2.0:metadata"
,
'OrganizationName'
)->string_value;
}
sub
getNextProviderId {
my
$self
=
shift
;
my
$logout
=
shift
;
my
$providerId
;
eval
{
$providerId
= Lasso::Logout::get_next_providerID(
$logout
); };
if
($@) {
$self
->checkLassoError($@);
return
;
}
return
$providerId
;
}
sub
resetProviderIdIndex {
my
$self
=
shift
;
my
$logout
=
shift
;
eval
{ Lasso::Logout::reset_providerID_index(
$logout
); };
return
$self
->checkLassoError($@);
}
sub
createAuthnRequest {
my
(
$self
,
$req
,
$server
,
$idp
,
$method
,
$forceAuthn
,
$isPassive
,
$nameIDFormat
,
$allowProxiedAuthn
,
$signSSOMessage
,
$requestedAuthnContext
) =
@_
;
my
$proxyCount
;
my
$proxyRequestedAuthnContext
;
my
$login
=
$self
->createLogin(
$server
);
unless
(
$login
) {
$self
->logger->error(
'Unable to create Lasso login'
);
return
;
}
unless
(
$self
->initAuthnRequest(
$login
,
$idp
,
$method
) ) {
$self
->logger->error(
"Could not initiate authentication request on $idp"
);
return
;
}
if
(
my
$relaystate
=
$self
->storeRelayState(
$req
,
'urldc'
) ) {
$login
->msg_relayState(
$relaystate
);
$self
->logger->debug(
"Set $relaystate in RelayState"
);
}
my
$request
=
$login
->request();
if
(
$req
->data->{_proxiedSamlRequest} ) {
$self
->logger->debug(
"IDP Proxy mode detected"
);
eval
{
$proxyCount
=
$req
->data->{_proxiedSamlRequest}->Scoping()->ProxyCount();
};
if
(
defined
$proxyCount
) {
$self
->logger->debug(
"Found proxyCount $proxyCount in proxied request"
);
if
(
$proxyCount
eq 0 ) {
$self
->userLogger->error(
"SAML request cannot be proxied (ProxyCount 0)"
);
return
;
}
else
{
my
$scoping
=
$req
->data->{_proxiedSamlRequest}->Scoping();
$scoping
->ProxyCount(
$proxyCount
-- );
eval
{
$request
->Scoping(
$scoping
); };
}
}
eval
{
$isPassive
=
$req
->data->{_proxiedSamlRequest}->IsPassive(); };
eval
{
$forceAuthn
=
$req
->data->{_proxiedSamlRequest}->ForceAuthn(); };
eval
{
$proxyRequestedAuthnContext
=
$req
->data->{_proxiedSamlRequest}->RequestedAuthnContext();
};
}
if
(
$nameIDFormat
) {
$self
->logger->debug(
"Use NameIDFormat $nameIDFormat"
);
$request
->NameIDPolicy()->Format(
$nameIDFormat
);
}
$request
->NameIDPolicy()->AllowCreate(1);
if
(
$forceAuthn
) {
$self
->logger->debug(
"Force authentication on IDP"
);
$request
->ForceAuthn(1);
}
if
(
$isPassive
) {
$self
->logger->debug(
"Passive authentication on IDP"
);
$request
->IsPassive(1);
}
if
(
$signSSOMessage
== 0 ) {
$self
->logger->debug(
"SSO request will not be signed"
);
$self
->disableSignature(
$login
);
}
elsif
(
$signSSOMessage
== 1 ) {
$self
->logger->debug(
"SSO request will be signed"
);
$self
->forceSignature(
$login
);
}
else
{
$self
->logger->debug(
"SSO request signature according to metadata"
);
}
if
(
$proxyRequestedAuthnContext
) {
$self
->logger->debug(
"Use RequestedAuthnContext from proxied request"
);
$request
->RequestedAuthnContext(
$proxyRequestedAuthnContext
);
}
elsif
(
$requestedAuthnContext
) {
$self
->logger->debug(
"Request $requestedAuthnContext context"
);
eval
{
my
$context
= Lasso::Samlp2RequestedAuthnContext->new();
$context
->AuthnContextClassRef(
$requestedAuthnContext
);
$context
->Comparison(
"minimum"
);
$request
->RequestedAuthnContext(
$context
);
};
if
($@) {
$self
->checkLassoError($@);
return
;
}
}
my
$h
=
$self
->p->processHook(
$req
,
'samlGenerateAuthnRequest'
,
$idp
,
$login
);
return
if
(
$h
!= PE_OK );
unless
(
$self
->buildAuthnRequestMsg(
$login
) ) {
$self
->logger->error(
"Could not build authentication request on $idp"
);
return
;
}
if
(
$method
==
$self
->getHttpMethod(
"artifact-get"
)
or
$method
==
$self
->getHttpMethod(
"artifact-post"
) )
{
my
$artifact_id
=
$login
->get_artifact;
my
$artifact_message
=
$login
->get_artifact_message;
$self
->storeArtifact(
$artifact_id
,
$artifact_message
);
}
return
$login
;
}
sub
createLogin {
my
(
$self
,
$server
,
$dump
) =
@_
;
my
$login
;
if
(
$dump
) {
eval
{
$login
= Lasso::Login::new_from_dump(
$server
,
$dump
); };
}
else
{
eval
{
$login
= Lasso::Login->new(
$server
); };
}
if
($@) {
$self
->checkLassoError($@);
return
;
}
return
$login
;
}
sub
initAuthnRequest {
my
(
$self
,
$login
,
$idp
,
$method
) =
@_
;
eval
{ Lasso::Login::init_authn_request(
$login
,
$idp
,
$method
); };
return
$self
->checkLassoError($@);
}
sub
initIdpInitiatedAuthnRequest {
my
(
$self
,
$login
,
$idp
) =
@_
;
eval
{ Lasso::Login::init_idp_initiated_authn_request(
$login
,
$idp
); };
return
$self
->checkLassoError($@);
}
sub
buildAuthnRequestMsg {
my
(
$self
,
$login
) =
@_
;
eval
{ Lasso::Login::build_authn_request_msg(
$login
); };
return
$self
->checkLassoError($@);
}
sub
processAuthnRequestMsg {
my
(
$self
,
$login
,
$request
,
$level
) =
@_
;
$self
->lazy_load_message(
$request
);
eval
{ Lasso::Login::process_authn_request_msg(
$login
,
$request
); };
return
$self
->checkLassoError( $@,
$level
);
}
sub
processAuthnRequestMsgWithError {
my
(
$self
,
$login
,
$request
,
$level
) =
@_
;
$self
->lazy_load_message(
$request
);
eval
{ Lasso::Login::process_authn_request_msg(
$login
,
$request
); };
return
$self
->getLassoError( $@,
$level
);
}
sub
validateRequestMsg {
my
(
$self
,
$login
,
$auth
,
$consent
) =
@_
;
eval
{ Lasso::Login::validate_request_msg(
$login
,
$auth
,
$consent
); };
return
$self
->checkLassoError($@);
}
sub
buildAuthnResponseMsg {
my
(
$self
,
$login
) =
@_
;
eval
{ Lasso::Login::build_authn_response_msg(
$login
); };
return
$self
->checkLassoError($@);
}
sub
buildArtifactMsg {
my
(
$self
,
$login
,
$method
) =
@_
;
eval
{ Lasso::Login::build_artifact_msg(
$login
,
$method
); };
return
$self
->checkLassoError($@);
}
sub
buildAssertion {
my
(
$self
,
$req
,
$login
,
$authn_context
,
$notOnOrAfterTimeout
) =
@_
;
$notOnOrAfterTimeout
||=
$self
->conf->{timeout};
my
$time
=
$req
->sessionInfo->{_utime} ||
time
();
my
$timeout
=
$time
+
$notOnOrAfterTimeout
;
my
$authenticationInstant
=
$self
->timestamp2samldate(
$time
);
my
$reauthenticateOnOrAfter
=
$self
->timestamp2samldate(
$timeout
);
my
$issued_time
=
time
;
my
$notBefore
=
$self
->timestamp2samldate(
$issued_time
);
my
$notOnOrAfter
=
$self
->timestamp2samldate(
$issued_time
+
$notOnOrAfterTimeout
);
eval
{
Lasso::Login::build_assertion(
$login
,
$authn_context
,
$authenticationInstant
,
$reauthenticateOnOrAfter
,
$notBefore
,
$notOnOrAfter
);
};
return
$self
->checkLassoError($@);
}
sub
processAuthnResponseMsg {
my
(
$self
,
$login
,
$response
) =
@_
;
$self
->lazy_load_message(
$response
);
eval
{ Lasso::Login::process_authn_response_msg(
$login
,
$response
); };
return
$self
->checkLassoError($@);
}
sub
acceptSSO {
my
(
$self
,
$login
) =
@_
;
eval
{ Lasso::Login::accept_sso(
$login
); };
return
$self
->checkLassoError($@);
}
sub
getIssuer {
my
(
$self
,
$message
) =
@_
;
$message
=~ s/
&RelayState
=$//g;
my
$issuer
=
eval
{ Lasso::profile_get_issuer(
$message
) };
if
($@) {
$self
->logger->debug(
"lasso_profile_get_issuer: $@"
);
}
if
( !
$issuer
) {
$self
->logger->debug(
"lasso_profile_get_issuer could not find entityID"
.
" in incoming message"
);
return
;
}
return
$issuer
;
}
sub
storeRelayState {
my
(
$self
,
$req
,
@data
) =
@_
;
my
$infos
;
foreach
(
@data
) {
$infos
->{
$_
} =
$req
->{
$_
}
if
$req
->{
$_
};
}
return
unless
(
$infos
);
$infos
->{_type} =
"relaystate"
;
my
$time
=
time
();
my
$timeout
=
$self
->conf->{timeout};
my
$samlRelayStateTimeout
=
$self
->conf->{samlRelayStateTimeout} ||
$timeout
;
$infos
->{_utime} =
$time
+ (
$samlRelayStateTimeout
-
$timeout
);
my
$samlSessionInfo
=
$self
->getSamlSession(
undef
,
$infos
)
or
return
undef
;
my
$relaystate_id
=
$samlSessionInfo
->id;
return
$relaystate_id
;
}
sub
extractRelayState {
my
(
$self
,
$req
,
$relaystate
,
$relayStateURL
) =
@_
;
return
0
unless
$relaystate
;
if
(
$relayStateURL
and
$relaystate
=~ /^https?:\/\// ) {
$self
->logger->debug(
"RelayState is a redirection URL: $relaystate"
);
$req
->{urldc} =
$relaystate
;
return
1;
}
else
{
my
$samlSessionInfo
=
$self
->getSamlSession(
$relaystate
);
return
0
unless
$samlSessionInfo
;
foreach
(
keys
%{
$samlSessionInfo
->data } ) {
next
if
$_
=~ /(type|_session_id|_utime)/;
if
(
$_
eq
'issuerUrldc'
) {
$req
->urldc(
$samlSessionInfo
->data->{
$_
} );
}
else
{
$req
->{
$_
} =
$samlSessionInfo
->data->{
$_
};
}
}
if
(
$samlSessionInfo
->remove ) {
$self
->logger->debug(
"Relaystate $relaystate was deleted"
);
}
else
{
$self
->logger->error(
"Unable to delete relaystate $relaystate"
);
$self
->logger->error(
$samlSessionInfo
->error );
}
}
return
1;
}
sub
getAssertion {
my
(
$self
,
$login
) =
@_
;
my
$assertion
;
eval
{
$assertion
= Lasso::Login::get_assertion(
$login
); };
if
($@) {
$self
->checkLassoError($@);
return
;
}
return
$assertion
;
}
sub
getAttributeValue {
my
(
$self
,
$name
,
$format
,
$friendly_name
,
$attributes
,
$force_utf8
) =
@_
;
my
$value
;
foreach
(
@$attributes
) {
my
$attr_name
=
$_
->Name();
my
$attr_format
=
$_
->NameFormat();
my
$attr_fname
=
$_
->FriendlyName();
next
if
(
$name
ne
$attr_name
);
next
if
(
$format
and
$format
ne
$attr_format
);
next
if
(
$friendly_name
and
$friendly_name
ne
$attr_fname
);
my
@attr_values
=
$_
->AttributeValue();
$value
=
join
(
$self
->{conf}->{multiValuesSeparator},
map
{
eval
{
$_
->any->content }
|| ()
}
@attr_values
);
}
return
$value
;
}
sub
validateConditions {
my
(
$self
,
$assertion
,
$entityID
,
$checkTime
,
$checkAudience
) =
@_
;
my
$tolerance
= 10;
my
$status
;
$checkTime
= 1
unless
defined
$checkTime
;
$checkAudience
= 1
unless
defined
$checkAudience
;
if
(
$checkTime
) {
eval
{
$status
= Lasso::Saml2Assertion::validate_time_checks(
$assertion
,
$tolerance
);
};
if
($@) {
$self
->checkLassoError($@);
return
0;
}
unless
(
$status
eq Lasso::Constants::SAML2_ASSERTION_VALID ) {
$self
->logger->error(
"Time conditions validation failed ($status)"
);
return
0;
}
$self
->logger->debug(
"Time conditions validated"
);
}
else
{
$self
->logger->debug(
"Time conditions not checked"
);
}
if
(
$checkAudience
) {
eval
{
$status
=
Lasso::Saml2Assertion::validate_audience(
$assertion
,
$entityID
);
};
if
($@) {
$self
->checkLassoError($@);
return
0;
}
unless
(
$status
eq Lasso::Constants::SAML2_ASSERTION_VALID ) {
$self
->logger->error(
"Audience conditions validations result: $status"
);
return
0;
}
$self
->logger->debug(
"Audience conditions validated"
);
}
else
{
$self
->logger->debug(
"Audience conditions not checked"
);
}
return
1;
}
sub
createLogoutRequest {
my
(
$self
,
$req
,
$server
,
$session_dump
,
$method
,
$signSLOMessage
) =
@_
;
my
$session
;
my
$logout
=
$self
->createLogout(
$server
);
unless
(
$self
->setSessionFromDump(
$logout
,
$session_dump
) ) {
$self
->logger->error(
"Could not fill Lasso::Logout with session dump"
);
return
;
}
unless
(
$self
->initLogoutRequest(
$logout
,
undef
,
$method
) ) {
$self
->logger->error(
"Could not initiate logout request"
);
return
;
}
if
(
my
$relaystate
=
$self
->storeRelayState(
$req
,
'urldc'
,
'issuerUrldc'
) )
{
$logout
->msg_relayState(
$relaystate
);
$self
->logger->debug(
"Set $relaystate in RelayState"
);
}
if
(
$signSLOMessage
== 0 ) {
$self
->logger->debug(
"SLO request will not be signed"
);
$self
->disableSignature(
$logout
);
}
elsif
(
$signSLOMessage
== 1 ) {
$self
->logger->debug(
"SLO request will be signed"
);
$self
->forceSignature(
$logout
);
}
else
{
$self
->logger->debug(
"SLO request signature according to metadata"
);
}
unless
(
$self
->buildLogoutRequestMsg(
$logout
) ) {
$self
->logger->error(
"Could not build logout request"
);
return
;
}
return
$logout
;
}
sub
createLogout {
my
(
$self
,
$server
,
$dump
) =
@_
;
my
$logout
;
if
(
$dump
) {
eval
{
$logout
= Lasso::Logout::new_from_dump(
$server
,
$dump
); };
}
else
{
eval
{
$logout
= Lasso::Logout->new(
$server
); };
}
if
($@) {
$self
->checkLassoError($@);
return
;
}
return
$logout
;
}
sub
initLogoutRequest {
my
(
$self
,
$logout
,
$entityID
,
$method
) =
@_
;
eval
{ Lasso::Logout::init_request(
$logout
,
$entityID
,
$method
); };
return
$self
->checkLassoError($@);
}
sub
buildLogoutRequestMsg {
my
(
$self
,
$logout
) =
@_
;
eval
{ Lasso::Logout::build_request_msg(
$logout
); };
return
$self
->checkLassoError($@);
}
sub
setSessionFromDump {
my
(
$self
,
$profile
,
$dump
) =
@_
;
$self
->logger->debug(
"Loading Session dump: $dump"
)
if
$dump
;
eval
{ Lasso::Profile::set_session_from_dump(
$profile
,
$dump
); };
return
$self
->checkLassoError($@);
}
sub
setIdentityFromDump {
my
(
$self
,
$profile
,
$dump
) =
@_
;
eval
{ Lasso::Profile::set_identity_from_dump(
$profile
,
$dump
); };
return
$self
->checkLassoError($@);
}
sub
getMetaDataURL {
my
(
$self
,
$key
,
$index
,
$full
) =
@_
;
$index
= 3
unless
defined
$index
;
$full
= 0
unless
defined
$full
;
return
''
unless
defined
$self
->conf->{
$key
};
my
$url
= (
split
( /;/,
$self
->conf->{
$key
} ) )[
$index
] ||
''
;
my
$portal
=
$self
->p->buildUrl;
$portal
=~ s/\/$//;
$url
=~ s/
return
$url
if
$full
;
my
$uri
= URI->new(
$url
);
return
$uri
->path();
}
sub
getRouteFromMetaDataURL {
my
(
$self
,
$key
,
$index
,
$sub
) =
@_
;
my
$uri
=
$self
->getMetaDataURL(
$key
,
$index
, 0 );
unless
(
$uri
=~ m
$self
->logger->debug(
"$key has no index $index"
);
return
();
}
my
@t
=
grep
/\w/,
split
( /\//,
$uri
);
my
$h
= {
pop
(
@t
) =>
$sub
};
while
(
my
$s
=
pop
@t
) {
$h
= {
$s
=>
$h
};
}
return
%$h
;
}
sub
addRouteFromMetaDataURL {
my
(
$self
,
@args
) =
@_
;
$self
->addAuthRouteFromMetaDataURL(
@args
);
$self
->addUnauthRouteFromMetaDataURL(
@args
);
}
sub
addAuthRouteFromMetaDataURL {
my
(
$self
,
$key
,
$index
,
$sub
,
$methods
) =
@_
;
my
%route
=
$self
->getRouteFromMetaDataURL(
$key
,
$index
,
$sub
);
return
unless
(
%route
);
$self
->addAuthRoute(
%route
,
$methods
);
}
sub
addUnauthRouteFromMetaDataURL {
my
(
$self
,
$key
,
$index
,
$sub
,
$methods
) =
@_
;
my
%route
=
$self
->getRouteFromMetaDataURL(
$key
,
$index
,
$sub
);
return
unless
(
%route
);
$self
->addUnauthRoute(
%route
,
$methods
);
}
sub
processLogoutResponseMsg {
my
(
$self
,
$logout
,
$response
) =
@_
;
$self
->lazy_load_message(
$response
);
eval
{ Lasso::Logout::process_response_msg(
$logout
,
$response
); };
return
$self
->checkLassoError($@);
}
sub
processLogoutRequestMsg {
my
(
$self
,
$logout
,
$request
) =
@_
;
$self
->lazy_load_message(
$request
);
eval
{ Lasso::Logout::process_request_msg(
$logout
,
$request
); };
return
0
unless
$self
->checkLassoError($@);
my
$notOnOrAfter
;
eval
{
$notOnOrAfter
=
$logout
->request()->NotOnOrAfter(); };
return
1
if
( $@ or !
$notOnOrAfter
);
$self
->logger->debug(
"Found NotOnOrAfter $notOnOrAfter in logout request"
);
my
$expirationTime
=
$self
->samldate2timestamp(
$notOnOrAfter
);
return
(
time
<
$expirationTime
);
}
sub
validateLogoutRequest {
my
(
$self
,
$logout
) =
@_
;
eval
{ Lasso::Logout::validate_request(
$logout
); };
return
$self
->checkLassoError($@);
}
sub
buildLogoutResponseMsg {
my
(
$self
,
$logout
) =
@_
;
eval
{ Lasso::Logout::build_response_msg(
$logout
); };
return
$self
->checkLassoError($@);
}
sub
storeReplayProtection {
my
(
$self
,
$samlID
,
$samlData
) =
@_
;
my
$infos
= {
type
=>
'assertion'
,
_utime
=>
time
(),
_assert_id
=>
$samlID
,
};
if
(
defined
$samlData
&&
$samlData
) {
$infos
->{data} =
$samlData
;
}
my
$samlSessionInfo
=
$self
->getSamlSession(
undef
,
$infos
);
return
0
unless
$samlSessionInfo
;
my
$session_id
=
$samlSessionInfo
->id;
$self
->logger->debug(
"Keep request ID $samlID in assertion session $session_id"
);
return
1;
}
sub
replayProtection {
my
(
$self
,
$samlID
) =
@_
;
unless
(
$samlID
) {
$self
->userLogger->error(
"Cannot verify replay because no SAML ID given"
);
return
0;
}
my
$sessions
=
Lemonldap::NG::Common::Apache::Session->searchOn(
$self
->amOpts,
"_assert_id"
,
$samlID
);
if
(
my
@keys
=
grep
{
$sessions
->{
$_
}->{_session_kind} eq
$self
->sessionKind }
keys
%$sessions
)
{
foreach
(
@keys
) {
next
unless
(
$sessions
->{
$_
}->{_session_kind} eq
$self
->sessionKind );
my
$session
=
$_
;
my
$result
= 1;
my
$samlSessionInfo
=
$self
->getSamlSession(
$_
);
return
0
unless
$samlSessionInfo
;
if
(
defined
$samlSessionInfo
->data->{data} ) {
$result
=
$samlSessionInfo
->data->{data};
}
if
(
$samlSessionInfo
->remove ) {
$self
->logger->debug(
"Assertion session $session (Message ID $samlID) was deleted"
);
return
$result
;
}
else
{
$self
->logger->error(
"Unable to delete assertion session $session (Message ID $samlID)"
);
$self
->logger->error(
$samlSessionInfo
->error );
return
0;
}
}
}
else
{
$self
->logger->
warn
(
"No assertion session found for request ID "
.
$samlID
);
}
return
0;
}
sub
resolveArtifact {
my
(
$self
,
$profile
,
$artifact
,
$method
) =
@_
;
my
$message
;
if
(
$profile
->isa(
"Lasso::Login"
) ) {
eval
{ Lasso::Login::init_request(
$profile
,
$artifact
,
$method
); };
return
unless
$self
->checkLassoError($@);
eval
{ Lasso::Login::build_request_msg(
$profile
); };
return
unless
$self
->checkLassoError($@);
unless
(
$profile
->msg_url ) {
$self
->logger->error(
"No artifact resolution URL found"
);
return
;
}
my
$request
= HTTP::Request->new(
'POST'
=>
$profile
->msg_url );
$request
->content_type(
'text/xml'
);
$request
->header(
Accept
=>
'text/xml'
);
$request
->content(
$profile
->msg_body );
$self
->logger->debug(
"Send message "
.
$profile
->msg_body .
" to "
.
$profile
->msg_url );
my
$soap_answer
=
$self
->ua->request(
$request
);
if
(
$soap_answer
->code() ==
"200"
) {
$message
=
$soap_answer
->content();
$self
->logger->debug(
"Get message $message"
);
}
else
{
$self
->logger->error(
"Error while sending message: "
.
$soap_answer
->status_line );
}
}
return
$message
;
}
sub
storeArtifact {
my
(
$self
,
$id
,
$message
,
$session_id
) =
@_
;
my
$infos
= {
type
=>
'artifact'
,
_utime
=>
time
(),
_art_id
=>
$id
,
message
=>
$message
,
};
$infos
->{_saml_id} =
$session_id
if
$session_id
;
my
$samlSessionInfo
=
$self
->getSamlSession(
undef
,
$infos
) or
return
0;
return
0
unless
$samlSessionInfo
;
my
$art_session_id
=
$samlSessionInfo
->id;
$self
->logger->debug(
"Keep artifact $id in session $art_session_id"
);
return
1;
}
sub
loadArtifact {
my
(
$self
,
$id
) =
@_
;
my
$art_session
;
unless
(
$id
) {
$self
->logger->error(
"Cannot load artifact because no id given"
);
return
;
}
my
$sessions
=
Lemonldap::NG::Common::Apache::Session->searchOn(
$self
->amOpts,
"_art_id"
,
$id
);
if
(
my
@keys
=
grep
{
$sessions
->{
$_
}->{_session_kind} eq
$self
->sessionKind }
keys
%$sessions
)
{
my
$nb_sessions
=
$#keys
+ 1;
$self
->logger->debug(
"Found $nb_sessions sessions for artifact $id"
);
return
if
(
$nb_sessions
!= 1 );
my
$session_id
=
shift
@keys
;
my
$session
=
$session_id
;
my
$samlSessionInfo
=
$self
->getSamlSession(
$session_id
);
return
unless
$samlSessionInfo
;
foreach
(
keys
%{
$samlSessionInfo
->data } ) {
$art_session
->{
$_
} =
$samlSessionInfo
->data->{
$_
};
}
if
(
$samlSessionInfo
->remove ) {
$self
->logger->debug(
"Artifact session $session (ID $id) was deleted"
);
return
$art_session
;
}
else
{
$self
->logger->error(
"Unable to delete artifact session $session (ID $id)"
);
$self
->logger->error(
$samlSessionInfo
->error );
return
;
}
}
return
;
}
sub
createArtifactResponse {
my
(
$self
,
$req
,
$login
) =
@_
;
my
$artifact_id
=
$login
->assertionArtifact();
my
$art_session
=
$self
->loadArtifact(
$artifact_id
);
utf8::decode(
$art_session
->{message} );
eval
{
$login
->set_artifact_message(
$art_session
->{message} ); };
if
($@) {
$self
->checkLassoError($@);
$self
->logger->error(
"Cannot load artifact message"
);
return
;
}
$self
->logger->debug(
"Response loaded"
);
my
$session_id
=
$art_session
->{_saml_id};
if
(
$session_id
) {
$self
->logger->debug(
"Find session_id $session_id in artifact session"
);
my
$session
=
$self
->p->getApacheSession(
$session_id
);
unless
(
$session
) {
$self
->logger->error(
"Unable to open session $session_id"
);
return
;
}
my
$lassoSession
=
$session
->data->{
$self
->lsDump };
if
(
$lassoSession
) {
unless
(
$self
->setSessionFromDump(
$login
,
$lassoSession
) ) {
$self
->logger->error(
"Unable to load Lasso Session"
);
return
;
}
$self
->logger->debug(
"Lasso Session loaded"
);
}
}
else
{
$self
->logger->debug(
"No session_id in artifact session"
);
}
eval
{ Lasso::Login::build_response_msg(
$login
); };
if
($@) {
$self
->checkLassoError($@);
$self
->logger->error(
"Cannot build artifact response"
);
return
;
}
$self
->logger->debug(
"Artifact response built"
);
if
(
$session_id
and
$login
->is_session_dirty ) {
$self
->logger->debug(
"Save Lasso session in session"
);
$self
->updateSession(
$req
,
{
$self
->
lsDump
=>
$login
->get_session->
dump
},
$session_id
);
}
return
$login
->msg_body;
}
sub
processArtRequestMsg {
my
(
$self
,
$profile
,
$request
) =
@_
;
if
(
$profile
->isa(
"Lasso::Login"
) ) {
eval
{ Lasso::Login::process_request_msg(
$profile
,
$request
); };
return
$self
->checkLassoError($@);
}
return
0;
}
sub
processArtResponseMsg {
my
(
$self
,
$profile
,
$response
) =
@_
;
if
(
$profile
->isa(
"Lasso::Login"
) ) {
eval
{ Lasso::Login::process_response_msg(
$profile
,
$response
); };
return
$self
->checkLassoError($@);
}
return
0;
}
sub
sendSOAPMessage {
my
(
$self
,
$endpoint
,
$message
) =
@_
;
my
$response
;
my
$request
= HTTP::Request->new(
'POST'
=>
$endpoint
);
$request
->content_type(
'text/xml'
);
$request
->header(
Accept
=>
'text/xml'
);
$request
->content(
$message
);
$self
->logger->debug(
"Send SOAP message $message to $endpoint"
);
my
$soap_answer
=
$self
->ua()->request(
$request
);
if
(
$soap_answer
->code() ==
"200"
) {
$response
=
$soap_answer
->content();
$self
->logger->debug(
"Get response $response"
);
}
else
{
$self
->logger->debug(
"No response to SOAP request"
);
return
;
}
return
$response
;
}
sub
createAssertionQuery {
my
(
$self
,
$server
) =
@_
;
my
$query
;
eval
{
$query
= Lasso::AssertionQuery->new(
$server
); };
if
($@) {
$self
->checkLassoError($@);
return
;
}
return
$query
;
}
sub
createAttributeRequest {
my
(
$self
,
$server
,
$idp
,
$attributes
,
$nameid
) =
@_
;
my
$query
;
return
unless
(
$query
=
$self
->createAssertionQuery(
$server
) );
$self
->logger->debug(
"Assertion query created"
);
my
$method
= Lasso::Constants::HTTP_METHOD_SOAP;
my
$type
= Lasso::Constants::ASSERTION_QUERY_REQUEST_TYPE_ATTRIBUTE;
eval
{
Lasso::AssertionQuery::init_request(
$query
,
$idp
,
$method
,
$type
);
};
if
($@) {
$self
->checkLassoError($@);
return
;
}
$self
->logger->debug(
"Assertion query request initiated"
);
eval
{
$query
->request()->Subject()->NameID(
$nameid
); };
if
($@) {
$self
->checkLassoError($@);
return
;
}
$self
->logger->debug(
"Set NameID "
.
$nameid
->
dump
.
" in assertion query"
);
my
@requested_attributes
;
foreach
(
keys
%$attributes
) {
my
$attribute
;
eval
{
$attribute
= Lasso::Saml2Attribute->new(); };
if
($@) {
$self
->checkLassoError($@);
return
;
}
my
(
$mandatory
,
$name
,
$format
,
$friendly_name
) =
split
( /;/,
$attributes
->{
$_
} );
$attribute
->Name(
$name
)
if
defined
$name
;
$attribute
->NameFormat(
$format
)
if
defined
$format
;
$attribute
->FriendlyName(
$friendly_name
)
if
defined
$friendly_name
;
push
@requested_attributes
,
$attribute
;
}
eval
{
$query
->request()->Attribute(
@requested_attributes
); };
if
($@) {
$self
->checkLassoError($@);
return
;
}
eval
{ Lasso::AssertionQuery::build_request_msg(
$query
); };
if
($@) {
$self
->checkLassoError($@);
return
;
}
return
$query
;
}
sub
validateAttributeRequest {
my
(
$self
,
$query
) =
@_
;
eval
{ Lasso::AssertionQuery::validate_request(
$query
); };
return
$self
->checkLassoError($@);
}
sub
processAttributeRequest {
my
(
$self
,
$server
,
$request
) =
@_
;
my
$query
;
return
unless
(
$query
=
$self
->createAssertionQuery(
$server
) );
$self
->logger->debug(
"Assertion query created"
);
eval
{ Lasso::AssertionQuery::process_request_msg(
$query
,
$request
); };
if
($@) {
$self
->checkLassoError($@);
return
;
}
$self
->logger->debug(
"Attribute request is valid"
);
return
$query
;
}
sub
buildAttributeResponse {
my
(
$self
,
$query
) =
@_
;
eval
{ Lasso::AssertionQuery::build_response_msg(
$query
); };
if
($@) {
$self
->checkLassoError($@);
return
;
}
return
$query
->msg_body;
}
sub
processAttributeResponse {
my
(
$self
,
$server
,
$response
) =
@_
;
my
$query
;
return
unless
(
$query
=
$self
->createAssertionQuery(
$server
) );
$self
->logger->debug(
"Assertion query created"
);
eval
{ Lasso::AssertionQuery::process_response_msg(
$query
,
$response
); };
if
($@) {
$self
->checkLassoError($@);
return
;
}
$self
->logger->debug(
"Attribute response is valid"
);
return
$query
;
}
sub
getNameIDFormat {
my
(
$self
,
$format
) =
@_
;
return
Lasso::Constants::SAML2_NAME_IDENTIFIER_FORMAT_UNSPECIFIED
if
(
$format
=~ /unspecified/i );
return
Lasso::Constants::SAML2_NAME_IDENTIFIER_FORMAT_EMAIL
if
(
$format
=~ /email/i );
return
Lasso::Constants::SAML2_NAME_IDENTIFIER_FORMAT_X509
if
(
$format
=~ /x509/i );
return
Lasso::Constants::SAML2_NAME_IDENTIFIER_FORMAT_WINDOWS
if
(
$format
=~ /windows/i );
return
Lasso::Constants::SAML2_NAME_IDENTIFIER_FORMAT_KERBEROS
if
(
$format
=~ /kerberos/i );
return
Lasso::Constants::SAML2_NAME_IDENTIFIER_FORMAT_ENTITY
if
(
$format
=~ /entity/i );
return
Lasso::Constants::SAML2_NAME_IDENTIFIER_FORMAT_PERSISTENT
if
(
$format
=~ /persistent/i );
return
Lasso::Constants::SAML2_NAME_IDENTIFIER_FORMAT_TRANSIENT
if
(
$format
=~ /transient/i );
return
Lasso::Constants::SAML2_NAME_IDENTIFIER_FORMAT_ENCRYPTED
if
(
$format
=~ /encrypted/i );
return
;
}
sub
getHttpMethod {
my
(
$self
,
$method
) =
@_
;
$method
||=
''
;
return
Lasso::Constants::HTTP_METHOD_POST
if
(
$method
=~ /^(http)?[-_]?post$/i );
return
Lasso::Constants::HTTP_METHOD_REDIRECT
if
(
$method
=~ /^(http)?[-_]?redirect$/i );
return
Lasso::Constants::HTTP_METHOD_SOAP
if
(
$method
=~ /^(http)?[-_]?soap$/i );
return
Lasso::Constants::HTTP_METHOD_ARTIFACT_GET
if
(
$method
=~ /^(artifact)[-_]get$/i );
return
Lasso::Constants::HTTP_METHOD_ARTIFACT_POST
if
(
$method
=~ /^(artifact)[-_]post$/i );
return
;
}
sub
getHttpMethodString {
my
(
$self
,
$method
) =
@_
;
return
"POST"
if
(
$method
== Lasso::Constants::HTTP_METHOD_POST );
return
"REDIRECT"
if
(
$method
== Lasso::Constants::HTTP_METHOD_REDIRECT );
return
"SOAP"
if
(
$method
== Lasso::Constants::HTTP_METHOD_SOAP );
return
"ARTIFACT GET"
if
(
$method
== Lasso::Constants::HTTP_METHOD_ARTIFACT_GET );
return
"ARTIFACT POST"
if
(
$method
== Lasso::Constants::HTTP_METHOD_ARTIFACT_POST );
return
"UNDEFINED"
;
}
sub
getFirstHttpMethod {
my
(
$self
,
$server
,
$entityID
,
$protocolType
) =
@_
;
my
$entity_provider
;
my
$method
;
eval
{
$entity_provider
= Lasso::Server::get_provider(
$server
,
$entityID
);
};
if
($@) {
$self
->checkLassoError($@);
return
;
}
eval
{
$method
=
Lasso::Provider::get_first_http_method(
$server
,
$entity_provider
,
$protocolType
);
};
if
($@) {
$self
->checkLassoError($@);
return
;
}
return
$method
;
}
sub
disableSignature {
my
(
$self
,
$profile
) =
@_
;
eval
{
Lasso::Profile::set_signature_hint(
$profile
,
Lasso::Constants::PROFILE_SIGNATURE_HINT_FORBID );
};
return
$self
->checkLassoError($@);
}
sub
forceSignature {
my
(
$self
,
$profile
) =
@_
;
eval
{
Lasso::Profile::set_signature_hint(
$profile
,
Lasso::Constants::PROFILE_SIGNATURE_HINT_FORCE );
};
return
$self
->checkLassoError($@);
}
sub
disableSignatureVerification {
my
(
$self
,
$profile
) =
@_
;
eval
{
Lasso::Profile::set_signature_verify_hint(
$profile
,
Lasso::Constants::PROFILE_SIGNATURE_VERIFY_HINT_IGNORE );
};
return
$self
->checkLassoError($@);
}
sub
forceSignatureVerification {
my
(
$self
,
$profile
) =
@_
;
eval
{
Lasso::Profile::set_signature_verify_hint(
$profile
,
Lasso::Constants::PROFILE_SIGNATURE_VERIFY_HINT_MAYBE );
};
return
$self
->checkLassoError($@);
}
sub
getAuthnContext {
my
(
$self
,
$context
) =
@_
;
return
Lasso::Constants::SAML2_AUTHN_CONTEXT_KERBEROS
if
(
$context
=~ /^kerberos$/i );
return
Lasso::Constants::SAML2_AUTHN_CONTEXT_PASSWORD_PROTECTED_TRANSPORT
if
(
$context
=~ /^password[-_ ]protected[-_ ]transport$/i );
return
Lasso::Constants::SAML2_AUTHN_CONTEXT_PASSWORD
if
(
$context
=~ /^password$/i );
return
Lasso::Constants::SAML2_AUTHN_CONTEXT_X509
if
(
$context
=~ /^x509$/i );
return
Lasso::Constants::SAML2_AUTHN_CONTEXT_TLS_CLIENT
if
(
$context
=~ /^tls[-_ ]client$/i );
return
Lasso::Constants::SAML2_AUTHN_CONTEXT_UNSPECIFIED
if
(
$context
=~ /^unspecified$/i );
return
;
}
sub
timestamp2samldate {
my
(
$self
,
$timestamp
) =
@_
;
my
@t
=
gmtime
(
$timestamp
);
my
$samldate
= strftime(
"%Y-%m-%dT%TZ"
,
@t
);
$self
->logger->debug(
"Convert timestamp $timestamp in SAML2 date: $samldate"
);
return
$samldate
;
}
sub
samldate2timestamp {
my
(
$self
,
$samldate
) =
@_
;
my
(
$year
,
$mon
,
$mday
,
$hour
,
$min
,
$sec
,
$msec
,
$ztime
) = (
$samldate
=~
/(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(\.\d+)?(Z)?/ );
my
$timestamp
=
timegm(
$sec
,
$min
,
$hour
,
$mday
,
$mon
- 1,
$year
- 1900, 0 );
$self
->logger->debug(
"Convert SAML2 date $samldate in timestamp: $timestamp"
);
return
$timestamp
;
}
sub
sendLogoutResponseToServiceProvider {
my
(
$self
,
$req
,
$logout
,
$method
) =
@_
;
unless
(
$self
->buildLogoutResponseMsg(
$logout
) ) {
$self
->logger->
warn
(
"Could not build a logout response for provider "
.
$logout
->remote_providerID
.
", staying on portal"
);
return
$self
->p->
do
(
$req
, [
sub
{ PE_LOGOUT_OK } ] );
}
if
(
$method
== Lasso::Constants::HTTP_METHOD_REDIRECT ) {
my
$slo_url
=
$logout
->msg_url;
return
[ 302, [
Location
=> URI->new(
$slo_url
)->as_string ], [] ];
}
elsif
(
$method
== Lasso::Constants::HTTP_METHOD_POST ) {
my
$slo_url
=
$logout
->msg_url;
my
$slo_body
=
$logout
->msg_body;
my
$relaystate
=
$logout
->msg_relayState;
$req
->postUrl(
$slo_url
);
$req
->{postFields} = {
'SAMLResponse'
=>
$slo_body
};
if
(
$relaystate
) {
$req
->{postFields}->{
'RelayState'
} = encode_entities(
$relaystate
);
$req
->data->{safeHiddenFormValues}->{RelayState} = 1;
}
return
$self
->p->
do
(
$req
, [
'autoPost'
] );
}
elsif
(
$method
== Lasso::Constants::HTTP_METHOD_SOAP ) {
return
$self
->sendSLOSoapErrorResponse(
$req
,
$logout
,
$method
);
}
return
$self
->p->sendError(
$req
,
"Lasso method '$method' should not be handle here..."
, 400 );
}
sub
sendLogoutRequestToProvider {
my
(
$self
,
$req
,
$logout
,
$providerID
,
$method
,
$relay
,
$relayState
) =
@_
;
my
$server
=
$self
->lassoServer;
my
$info
;
if
( !
$providerID
) {
return
( 0,
undef
,
undef
);
}
$self
->lazy_load_entityid(
$providerID
);
my
$type
=
defined
$self
->spList->{
$providerID
} ?
"SP"
:
"IDP"
;
unless
(
defined
$self
->{
lc
(
$type
) .
'List'
}->{
$providerID
} ) {
$self
->logger->error(
"$providerID does not match any known $type"
);
return
( 0,
undef
,
undef
);
}
my
$providerName
=
$self
->{
lc
(
$type
) .
'List'
}->{
$providerID
}->{name};
my
$confKey
=
$self
->{
lc
(
$type
) .
'List'
}->{
$providerID
}->{confKey};
my
$protocolType
= Lasso::Constants::MD_PROTOCOL_TYPE_SINGLE_LOGOUT;
if
( !
$method
) {
$method
=
$self
->getFirstHttpMethod(
$server
,
$providerID
,
$protocolType
);
}
$relay
= 0
unless
(
defined
$relay
);
my
$signSLOMessage
=
$self
->{
lc
(
$type
) .
'Options'
}->{
$providerID
}
->{
'saml'
.
$type
.
'MetaDataOptionsSignSLOMessage'
} // -1;
if
(
$signSLOMessage
== 0 ) {
$self
->logger->debug(
"SLO request will not be signed"
);
$self
->disableSignature(
$logout
);
}
elsif
(
$signSLOMessage
== 1 ) {
$self
->logger->debug(
"SLO request will be signed"
);
$self
->forceSignature(
$logout
);
}
else
{
$self
->logger->debug(
"SLO request signature according to metadata"
);
}
if
(
$relayState
) {
eval
{
$logout
->msg_relayState(
$relayState
); };
if
($@) {
$self
->logger->error(
"Unable to set Relay State $relayState in SLO request for $confKey"
);
return
( 0,
$method
,
undef
);
}
$self
->logger->debug(
'Relay state set'
);
}
unless
(
defined
$method
and
$method
!= -1 ) {
$self
->logger->debug(
"No HTTP SLO method found for $providerID"
);
return
( 0,
$method
,
undef
);
}
unless
(
$self
->initLogoutRequest(
$logout
,
$providerID
,
$method
) ) {
$self
->logger->error(
"Initiate logout request failed for $providerID"
);
return
( 0,
$method
,
undef
);
}
unless
(
$self
->buildLogoutRequestMsg(
$logout
) ) {
$self
->logger->error(
"Build logout request failed for $providerID"
);
return
( 0,
$method
,
undef
);
}
$self
->logger->debug(
"Request built for $providerID"
);
my
$samlID
=
$logout
->request()->ID;
unless
(
$self
->storeReplayProtection(
$samlID
) ) {
$self
->logger->error(
"Unable to store message ID"
);
return
( 0,
$method
,
undef
);
}
my
$portal
=
$self
->p->buildUrl;
$portal
=~ s/\/$//;
if
(
$method
== Lasso::Constants::HTTP_METHOD_REDIRECT ) {
$self
->logger->debug(
"Send HTTP-REDIRECT logout request to $providerID"
);
my
$slo_url
=
$logout
->msg_url;
$info
.=
$self
->loadTemplate(
$req
,
'samlSpLogout'
,
params
=> {
url
=>
$slo_url
,
name
=>
$providerName
,
}
);
}
elsif
(
$method
== Lasso::Constants::HTTP_METHOD_POST ) {
$self
->logger->debug(
"Build POST relay logout request to $providerID"
);
my
$infos
;
$infos
->{type} =
'relay'
;
$infos
->{_utime} =
time
;
$infos
->{url} =
$logout
->msg_url;
$infos
->{body} =
$logout
->msg_body;
$infos
->{relayState} =
$logout
->msg_relayState;
my
$relayInfos
=
$self
->getSamlSession(
undef
,
$infos
);
my
$relayID
=
$relayInfos
->id;
my
$slo_url
=
$portal
.
'/saml/relaySingleLogoutPOST?'
. build_urlencoded(
relay
=>
$relayID
);
$info
.=
$self
->loadTemplate(
$req
,
'samlSpLogout'
,
params
=> {
url
=>
$slo_url
,
name
=>
$providerName
,
}
);
$req
->data->{cspChildSrc}->{
$self
->p->cspGetHost(
$logout
->msg_url ) }
= 1;
}
elsif
(
$method
== Lasso::Constants::HTTP_METHOD_SOAP ) {
if
(
$relay
) {
$self
->logger->debug(
"Build SOAP relay logout request for $providerID"
);
my
$infos
;
$infos
->{type} =
'relay'
;
$infos
->{_utime} =
time
;
$infos
->{
$self
->lsDump } =
$req
->sessionInfo->{
$self
->lsDump };
$infos
->{
$self
->liDump } =
$req
->sessionInfo->{
$self
->liDump };
$infos
->{_providerID} =
$providerID
;
$infos
->{_relayState} =
$logout
->msg_relayState;
my
$relayInfos
=
$self
->getSamlSession(
undef
,
$infos
);
my
$relayID
=
$relayInfos
->id;
my
$slo_url
=
$portal
.
'/saml/relaySingleLogoutSOAP?'
. build_urlencoded(
relay
=>
$relayID
);
$info
.=
$self
->loadTemplate(
$req
,
'samlSpSoapLogout'
,
params
=> {
imgUrl
=>
$slo_url
,
name
=>
$providerName
,
}
);
}
else
{
$self
->logger->debug(
"Send SOAP logout request to $providerID"
);
my
$slo_url
=
$logout
->msg_url;
my
$slo_body
=
$logout
->msg_body;
my
$sp_response
=
$self
->sendSOAPMessage(
$slo_url
,
$slo_body
);
unless
(
$sp_response
) {
$self
->logger->error(
"No logout response to SOAP request"
);
return
( 0,
$method
,
undef
);
}
my
$sp_result
=
$self
->processLogoutResponseMsg(
$logout
,
$sp_response
);
unless
(
$sp_result
) {
$self
->logger->error(
"Fail to process logout response"
);
return
( 0,
$method
,
undef
);
}
if
(
$relayState
) {
my
$sloStatusSessionInfos
=
$self
->getSamlSession(
$relayState
);
if
(
$sloStatusSessionInfos
) {
$sloStatusSessionInfos
->update( {
$confKey
=> 1 } );
$self
->logger->debug(
"Store SLO status for $confKey in session $relayState"
);
}
else
{
$self
->logger->
warn
(
"Unable to store SLO status for $confKey in session $relayState"
);
}
}
$self
->logger->debug(
"Logout response is valid"
);
}
}
return
( 1,
$method
,
$info
);
}
sub
sendLogoutRequestToProviders {
my
(
$self
,
$req
,
$logout
,
$relayState
) =
@_
;
my
$server
=
$self
->lassoServer;
my
$providersCount
= 0;
my
$content
=
''
;
$self
->resetProviderIdIndex(
$logout
);
while
(
my
$providerID
=
$self
->getNextProviderId(
$logout
) ) {
my
(
$rstatus
,
$rmethod
,
$rinfo
) =
$self
->sendLogoutRequestToProvider(
$req
,
$logout
,
$providerID
,
undef
, 1,
$relayState
);
next
unless
(
$rstatus
);
$providersCount
++;
if
(
$rinfo
) {
$content
.=
$rinfo
;
}
}
$req
->info(
$self
->loadTemplate(
$req
,
'samlSpsLogout'
,
params
=> {
content
=>
$content
}
)
)
if
$providersCount
;
return
$providersCount
;
}
sub
checkSignatureStatus {
my
(
$self
,
$profile
) =
@_
;
eval
{ Lasso::Profile::get_signature_status(
$profile
); };
return
$self
->checkLassoError($@);
}
sub
authnContext2authnLevel {
my
(
$self
,
$authnContext
) =
@_
;
return
$self
->conf->{samlAuthnContextMapPassword}
if
(
$authnContext
eq
$self
->getAuthnContext(
"password"
) );
return
$self
->conf->{samlAuthnContextMapPasswordProtectedTransport}
if
(
$authnContext
eq
$self
->getAuthnContext(
"password-protected-transport"
)
);
return
$self
->conf->{samlAuthnContextMapKerberos}
if
(
$authnContext
eq
$self
->getAuthnContext(
"kerberos"
) );
return
$self
->conf->{samlAuthnContextMapTLSClient}
if
(
$authnContext
eq
$self
->getAuthnContext(
"tls-client"
) );
if
(
defined
$self
->conf->{samlAuthnContextMapExtra}
and
defined
$self
->conf->{samlAuthnContextMapExtra}->{
$authnContext
} )
{
return
$self
->conf->{samlAuthnContextMapExtra}->{
$authnContext
};
}
return
0;
}
sub
authnLevel2authnContext {
my
(
$self
,
$authnLevel
) =
@_
;
return
$self
->getAuthnContext(
"password"
)
if
(
$authnLevel
==
$self
->conf->{samlAuthnContextMapPassword} );
return
$self
->getAuthnContext(
"password-protected-transport"
)
if
(
$authnLevel
==
$self
->conf->{samlAuthnContextMapPasswordProtectedTransport} );
return
$self
->getAuthnContext(
"kerberos"
)
if
(
$authnLevel
==
$self
->conf->{samlAuthnContextMapKerberos} );
return
$self
->getAuthnContext(
"tls-client"
)
if
(
$authnLevel
==
$self
->conf->{samlAuthnContextMapTLSClient} );
if
(
defined
$self
->conf->{samlAuthnContextMapExtra} ) {
foreach
my
$authnContext
(
keys
%{
$self
->conf->{samlAuthnContextMapExtra} } )
{
return
$authnContext
if
(
$authnLevel
==
$self
->conf->{samlAuthnContextMapExtra}->{
$authnContext
} );
}
}
return
$self
->getAuthnContext(
"unspecified"
);
}
sub
checkDestination {
my
(
$self
,
$message
,
$url
) =
@_
;
my
$destination
;
eval
{
$destination
=
$message
->Destination(); };
if
( $@ or !
$destination
) {
$self
->logger->debug(
"No Destination in SAML message"
);
return
1;
}
$self
->logger->debug(
"Destination $destination found in SAML message"
);
my
$portal
=
$self
->p->buildUrl;
$portal
=~ s
$url
=
$portal
.
$url
;
$url
=~ s/\?.*//;
if
(
$destination
eq
$url
) {
$self
->logger->debug(
"Destination match URL $url"
);
return
1;
}
$self
->logger->error(
"Destination does not match URL $url"
);
return
0;
}
sub
getSamlSession {
my
(
$self
,
$id
,
$info
) =
@_
;
my
$samlSession
= Lemonldap::NG::Common::Session->new( {
storageModule
=>
$self
->aModule,
storageModuleOptions
=>
$self
->amOpts,
cacheModule
=>
$self
->conf->{localSessionStorage},
cacheModuleOptions
=>
$self
->conf->{localSessionStorageOptions},
id
=>
$id
,
kind
=>
$self
->sessionKind,
(
$info
? (
info
=>
$info
) : () ),
}
);
if
(
$samlSession
->error ) {
if
(
$id
) {
$self
->userLogger->
warn
(
"SAML session $id isn't yet available"
);
}
else
{
$self
->logger->error(
"Unable to create new SAML session"
);
$self
->logger->error(
$samlSession
->error );
}
return
undef
;
}
return
$samlSession
;
}
sub
createAttribute {
my
(
$self
,
$name
,
$format
,
$friendly_name
) =
@_
;
my
$attribute
;
return
unless
defined
$name
;
eval
{
$attribute
= Lasso::Saml2Attribute->new(); };
if
($@) {
$self
->checkLassoError($@);
return
;
}
$friendly_name
||=
$name
;
$format
||= Lasso::Constants::SAML2_ATTRIBUTE_NAME_FORMAT_BASIC;
$attribute
->Name(
$name
);
$attribute
->NameFormat(
$format
);
$attribute
->FriendlyName(
$friendly_name
);
return
$attribute
;
}
sub
createAttributeValue {
my
(
$self
,
$value
,
$force_utf8
) =
@_
;
my
$saml2value
;
$force_utf8
= 1
unless
defined
(
$force_utf8
);
return
unless
defined
$value
;
$self
->logger->debug(
"Decode UTF8 value $value"
)
if
$force_utf8
;
$value
= decode(
"utf8"
,
$value
)
if
$force_utf8
;
$self
->logger->debug(
"Create attribute value $value"
);
eval
{
$saml2value
= Lasso::Saml2AttributeValue->new(); };
if
($@) {
$self
->checkLassoError($@);
return
;
}
my
@any
;
my
$textNode
;
eval
{
$textNode
= Lasso::MiscTextNode->new(); };
if
($@) {
$self
->checkLassoError($@);
return
;
}
$textNode
->text_child(1);
$textNode
->content(
$value
);
push
@any
,
$textNode
;
$saml2value
->any(
@any
);
return
$saml2value
;
}
sub
getEncryptionMode {
my
(
$self
,
$encryption_mode
) =
@_
;
return
Lasso::Constants::ENCRYPTION_MODE_NAMEID
if
(
$encryption_mode
=~ /^nameid$/i );
return
Lasso::Constants::ENCRYPTION_MODE_ASSERTION
if
(
$encryption_mode
=~ /^assertion$/i );
return
Lasso::Constants::ENCRYPTION_MODE_NONE;
}
sub
setProviderEncryptionMode {
my
(
$self
,
$provider
,
$encryption_mode
) =
@_
;
eval
{
Lasso::Provider::set_encryption_mode(
$provider
,
$encryption_mode
);
};
return
$self
->checkLassoError($@);
}
sub
updateSAMLSecondarySessions {
my
(
$self
,
$req
,
$old_session_id
,
$new_session_id
) =
@_
;
my
$saml_sessions
=
Lemonldap::NG::Common::Apache::Session->searchOn(
$self
->amOpts,
"_saml_id"
,
$old_session_id
);
if
(
my
@saml_sessions_keys
=
grep
{
$saml_sessions
->{
$_
}->{_session_kind} eq
$self
->sessionKind }
keys
%$saml_sessions
)
{
foreach
my
$saml_session
(
@saml_sessions_keys
) {
$self
->logger->debug(
"Retrieve SAML session $saml_session"
);
my
$samlSessionInfo
=
$self
->getSamlSession(
$saml_session
);
if
(
$samlSessionInfo
) {
$samlSessionInfo
->update( {
'_saml_id'
=>
$new_session_id
} );
}
}
}
return
;
}
sub
deleteSAMLSecondarySessions {
my
(
$self
,
$session_id
) =
@_
;
my
$result
= 1;
my
$saml_sessions
=
Lemonldap::NG::Common::Apache::Session->searchOn(
$self
->amOpts,
"_saml_id"
,
$session_id
);
if
(
my
@saml_sessions_keys
=
grep
{
$saml_sessions
->{
$_
}->{_session_kind} eq
$self
->sessionKind }
keys
%$saml_sessions
)
{
foreach
my
$saml_session
(
@saml_sessions_keys
) {
$self
->logger->debug(
"Retrieve SAML session $saml_session"
);
my
$samlSessionInfo
=
$self
->getSamlSession(
$saml_session
);
if
(
$samlSessionInfo
->remove ) {
$self
->logger->debug(
"SAML session $saml_session deleted"
);
}
else
{
$self
->logger->error(
"Unable to delete SAML session $saml_session"
);
$self
->logger->error(
$samlSessionInfo
->error );
$result
= 0;
}
}
}
elsif
(
$session_id
) {
$self
->logger->debug(
"No SAML session found for session $session_id"
);
}
return
$result
;
}
sub
sendSLOErrorResponse {
my
(
$self
,
$req
,
$logout
,
$method
) =
@_
;
my
$session
=
unless
(
$self
->setSessionFromDump(
$logout
,
$session
) ) {
return
$self
->p->sendError(
$req
,
"Could not set empty session in logout object"
, 500 );
}
return
$self
->sendLogoutResponseToServiceProvider(
$req
,
$logout
,
$method
);
}
sub
sendSLOSoapErrorResponse {
my
(
$self
,
$req
,
$logout
,
$method
) =
@_
;
my
$session
=
unless
(
$self
->setSessionFromDump(
$logout
,
$session
) ) {
return
$self
->p->sendError(
$req
,
"Could not set empty session in logout object"
);
}
my
$slo_body
=
$logout
->msg_body;
$self
->logger->debug(
"SOAP response $slo_body"
);
return
[
200,
[
'Content-Type'
=>
'text/xml'
,
'Content-Length'
=>
length
(
$slo_body
)
],
[
$slo_body
]
];
}
sub
getQueryString {
my
(
$self
,
$req
) =
@_
;
my
$query_string
;
if
(
$self
->conf->{samlUseQueryStringSpecific} ) {
my
@pairs
=
split
( /&/,
$req
->param(
'issuerQuery'
) ||
$req
->query_string );
$query_string
=
join
(
';'
,
@pairs
);
}
else
{
$query_string
=
$req
->param(
'issuerQuery'
) ||
$req
->query_string;
}
return
$query_string
;
}
sub
importRealSession {
my
(
$self
,
$req
,
$ssoSession
) =
@_
;
$req
->sessionInfo(
$ssoSession
->data );
$req
->id(
$ssoSession
->id );
$req
->user(
$ssoSession
->data->{
$self
->conf->{whatToTrace} } );
}
sub
metadata {
my
(
$self
,
$req
) =
@_
;
my
$type
=
$req
->param(
'type'
) ||
'all'
;
if
(
my
$metadata
= Lemonldap::NG::Common::Conf::SAML::Metadata->new() ) {
my
$s
=
$metadata
->serviceToXML( { %{
$self
->conf },
portal
=>
$req
->portal },
$type
);
return
[
200,
[
'Content-Type'
=>
'application/xml'
,
'Content-Length'
=>
length
(
$s
),
],
[
$s
]
];
}
return
$self
->p->sendError(
$req
,
'Unable to build Metadata'
, 500 );
}
sub
getSignatureMethod {
my
(
$self
,
$signature_method
) =
@_
;
my
$signature_method_rsa_sha1
=
eval
'Lasso::Constants::SIGNATURE_METHOD_RSA_SHA1'
;
my
$signature_method_rsa_sha256
=
eval
'Lasso::Constants::SIGNATURE_METHOD_RSA_SHA256'
;
my
$signature_method_rsa_sha384
=
eval
'Lasso::Constants::SIGNATURE_METHOD_RSA_SHA384'
;
my
$signature_method_rsa_sha512
=
eval
'Lasso::Constants::SIGNATURE_METHOD_RSA_SHA512'
;
my
$signature_method_none
=
eval
'Lasso::Constants::SIGNATURE_METHOD_NONE'
;
return
$signature_method_rsa_sha1
if
(
$signature_method
=~ /^RSA_SHA1$/i );
return
$signature_method_rsa_sha256
if
(
$signature_method
=~ /^RSA_SHA256$/i );
return
$signature_method_rsa_sha384
if
(
$signature_method
=~ /^RSA_SHA384$/i );
return
$signature_method_rsa_sha512
if
(
$signature_method
=~ /^RSA_SHA512$/i );
return
$signature_method_none
;
}
sub
setProviderSignatureMethod {
my
(
$self
,
$provider
,
$signature_method
) =
@_
;
my
$priv
=
$self
->conf->{samlServicePrivateKeySig};
my
$pass
=
$self
->conf->{samlServicePrivateKeySigPwd} ||
''
;
my
$cert
=
$self
->conf->{samlServicePublicKeySig};
eval
{
my
$key
= Lasso::Key::new_for_signature_from_file(
$priv
,
$pass
,
$signature_method
,
$cert
, );
$provider
->set_server_signing_key(
$key
);
};
return
$self
->checkLassoError($@);
}
1;