lemonldap-ng/modules/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/IssuerDBSAML.pm

1145 lines
38 KiB
Perl
Raw Normal View History

2009-04-07 22:38:24 +02:00
## @file
# SAML Issuer file
2009-04-07 22:38:24 +02:00
## @class
# SAML Issuer class
package Lemonldap::NG::Portal::IssuerDBSAML;
2009-04-07 22:38:24 +02:00
use strict;
use Lemonldap::NG::Common::Conf::SAML::Metadata;
2009-04-07 22:38:24 +02:00
use Lemonldap::NG::Portal::Simple;
use Lemonldap::NG::Portal::_SAML;
our @ISA = qw(Lemonldap::NG::Portal::_SAML);
2009-04-07 22:38:24 +02:00
our $VERSION = '0.01';
## @method void issuerDBInit()
# Load and check SAML configuration
# @return Lemonldap::NG::Portal error code
sub issuerDBInit {
2009-04-07 22:38:24 +02:00
my $self = shift;
2010-03-25 12:24:52 +01:00
# Load SAML service
return PE_ERROR unless $self->loadService();
# Load SAML identity providers
return PE_ERROR unless $self->loadSPs();
PE_OK;
2009-04-07 22:38:24 +02:00
}
## @apmethod int issuerForUnAuthUser()
2010-04-07 14:27:50 +02:00
# Check if there is an SAML authentication request
# Called only for unauthenticated users, check isPassive flag
2009-04-07 22:38:24 +02:00
# @return Lemonldap::NG::Portal error code
sub issuerForUnAuthUser {
2010-03-26 17:47:17 +01:00
my $self = shift;
2010-03-26 14:56:37 +01:00
my $server = $self->{_lassoServer};
# Get configuration parameter
2010-03-26 14:56:37 +01:00
my $saml_sso_soap_url =
$self->getMetaDataURL( "samlIDPSSODescriptorSingleSignOnServiceSOAP", 1 );
my $saml_sso_soap_url_ret =
$self->getMetaDataURL( "samlIDPSSODescriptorSingleSignOnServiceSOAP", 2 );
my $saml_sso_get_url =
$self->getMetaDataURL( "samlIDPSSODescriptorSingleSignOnServiceHTTP", 1 );
my $saml_sso_get_url_ret =
$self->getMetaDataURL( "samlIDPSSODescriptorSingleSignOnServiceHTTP", 2 );
my $saml_slo_soap_url =
$self->getMetaDataURL( "samlIDPSSODescriptorSingleLogoutServiceSOAP", 1 );
my $saml_slo_soap_url_ret =
$self->getMetaDataURL( "samlIDPSSODescriptorSingleLogoutServiceSOAP", 2 );
my $saml_slo_get_url =
$self->getMetaDataURL( "samlIDPSSODescriptorSingleLogoutServiceHTTP", 1 );
my $saml_slo_get_url_ret =
$self->getMetaDataURL( "samlIDPSSODescriptorSingleLogoutServiceHTTP", 2 );
2010-04-08 13:03:53 +02:00
my $saml_ars_url = $self->getMetaDataURL(
"samlIDPSSODescriptorArtifactResolutionServiceArtifact");
2010-05-04 12:10:34 +02:00
my $saml_slo_url_relay_soap =
$self->{portal} . '/saml/relaySingleLogoutSOAP';
2010-03-26 14:56:37 +01:00
2010-03-26 17:47:17 +01:00
# Get HTTP request informations to know
# if we are receving SAML request or response
my $url = $self->url();
my $request_method = $self->request_method();
my $content_type = $self->content_type();
2010-03-26 14:56:37 +01:00
# 1.1. SSO
if ( $url =~ /^(\Q$saml_sso_soap_url\E|\Q$saml_sso_get_url\E)$/io ) {
2010-03-26 14:56:37 +01:00
2010-03-26 17:47:17 +01:00
$self->lmLog( "URL $url detected as an SSO request URL", 'debug' );
2010-03-26 14:56:37 +01:00
# Check message
my ( $request, $response, $method, $relaystate, $artifact ) =
2010-03-26 17:47:17 +01:00
$self->checkMessage( $url, $request_method, $content_type );
2010-03-26 14:56:37 +01:00
# Create Login object
my $login = $self->createLogin($server);
2010-05-04 12:10:34 +02:00
# Ignore signature verification
$self->disableSignatureVerification($login);
# Process the request
if ($request) {
2010-03-26 14:56:37 +01:00
# Process authentication request
my $result;
if ($artifact) {
$result = $self->processArtRequestMsg( $login, $request );
}
else {
$result = $self->processAuthnRequestMsg( $login, $request );
}
unless ($result) {
$self->lmLog( "SSO: Fail to process authentication request",
'error' );
return PE_ERROR;
}
$self->lmLog( "SSO: authentication request is valid", 'debug' );
2010-05-04 12:10:34 +02:00
# Get SP entityID
my $sp = $login->remote_providerID();
$self->lmLog( "Found entityID $sp in SAML message", 'debug' );
# SP conf key
my $spConfKey = $self->{_spList}->{$sp}->{confKey};
unless ($spConfKey) {
$self->lmLog( "$sp do not match any SP in configuration",
'error' );
return PE_ERROR;
}
$self->lmLog( "$sp match $spConfKey SP in configuration", 'debug' );
# Do we check signature?
my $checkSSOMessageSignature =
$self->{samlSPMetaDataOptions}->{$spConfKey}
->{samlSPMetaDataOptionsCheckSSOMessageSignature};
if ($checkSSOMessageSignature) {
unless ( $self->checkSignatureStatus($login) ) {
$self->lmLog( "Signature is not valid", 'error' );
return PE_ERROR;
}
else {
$self->lmLog( "Signature is valid", 'debug' );
}
}
else {
$self->lmLog( "Message signature will not be checked",
'debug' );
}
2010-05-04 12:10:34 +02:00
2010-03-26 14:56:37 +01:00
# Get SAML request
my $saml_request = $login->request();
unless ($saml_request) {
$self->lmLog( "No SAML request found", 'error' );
return PE_ERROR;
}
# Check Destination
return PE_ERROR
unless ( $self->checkDestination( $saml_request, $url ) );
2010-03-26 14:56:37 +01:00
# Check isPassive flag
my $isPassive = $saml_request->IsPassive();
if ($isPassive) {
$self->lmLog(
"Found isPassive flag in SAML request, not compatible with unauthenticated user",
'error'
);
2010-03-26 14:56:37 +01:00
return PE_ERROR;
}
}
elsif ($response) {
$self->lmLog(
"Authentication responses are not managed by this module",
'debug' );
return PE_OK;
}
else {
# No request or response
# This should not happen
$self->lmLog( "No request or response found", 'debug' );
return PE_OK;
}
2010-03-26 14:56:37 +01:00
}
# 1.2. SLO
if ( $url =~
/^(\Q$saml_slo_soap_url\E|\Q$saml_slo_soap_url_ret\E|\Q$saml_slo_get_url\E|\Q$saml_slo_get_url_ret\E)$/io
)
{
$self->lmLog( "URL $url detected as an SLO URL", 'debug' );
# Check SAML Message
my ( $request, $response, $method, $relaystate, $artifact ) =
$self->checkMessage( $url, $request_method, $content_type, "logout" );
2010-05-04 12:10:34 +02:00
# Create Logout object
my $logout = $self->createLogout($server);
# Ignore signature verification
$self->disableSignatureVerification($logout);
if ($request) {
# Process logout request
unless ( $self->processLogoutRequestMsg( $logout, $request ) ) {
$self->lmLog( "SLO: Fail to process logout request", 'error' );
return PE_ERROR;
}
$self->lmLog( "SLO: Logout request is valid", 'debug' );
2010-05-04 12:10:34 +02:00
# Get SP entityID
my $sp = $logout->remote_providerID();
$self->lmLog( "Found entityID $sp in SAML message", 'debug' );
# SP conf key
my $spConfKey = $self->{_spList}->{$sp}->{confKey};
unless ($spConfKey) {
$self->lmLog( "$sp do not match any SP in configuration",
'error' );
return PE_ERROR;
}
$self->lmLog( "$sp match $spConfKey SP in configuration", 'debug' );
# Do we check signature?
my $checkSLOMessageSignature =
$self->{samlSPMetaDataOptions}->{$spConfKey}
->{samlSPMetaDataOptionsCheckSLOMessageSignature};
if ($checkSLOMessageSignature) {
unless ( $self->checkSignatureStatus($logout) ) {
$self->lmLog( "Signature is not valid", 'error' );
return PE_ERROR;
}
else {
$self->lmLog( "Signature is valid", 'debug' );
}
}
else {
$self->lmLog( "Message signature will not be checked",
'debug' );
}
2010-05-04 12:10:34 +02:00
# Get SAML request
my $saml_request = $logout->request();
unless ($saml_request) {
$self->lmLog( "No SAML request found", 'error' );
return PE_ERROR;
}
# Check Destination
return PE_ERROR
unless ( $self->checkDestination( $saml_request, $url ) );
# Set RelayState
if ($relaystate) {
$logout->msg_relayState($relaystate);
$self->lmLog( "Set $relaystate in RelayState", 'debug' );
}
# Signature
my $signSLOMessage =
$self->{samlSPMetaDataOptions}->{$spConfKey}
->{samlSPMetaDataOptionsSignSLOMessage};
unless ($signSLOMessage) {
$self->lmLog( "Do not sign this SLO response", 'debug' );
return PE_ERROR unless ( $self->disableSignature($logout) );
}
# Send logout response
return PE_ERROR
unless (
$self->sendLogoutResponseToServiceProvider(
$logout, $method, $relaystate, 0
)
);
}
}
# 1.3. SLO SOAP replay (send SOAP requests asynchronously)
# This URL is used by IMG html tag, and should returned PE_IMG_*
if ( $url =~ /^(\Q$saml_slo_url_relay_soap\E)/io ) {
$self->lmLog( "URL $url detected as a relay service URL", 'debug' );
# Check if relay parameter is present (mandatory)
my $samlID;
unless ( $samlID = $self->param('relay') ) {
$self->lmLog( "No relayID detected", 'error' );
2010-05-04 12:10:34 +02:00
return PE_IMG_NOK;
}
# Retrieve the corresponding data from samlStorage
my $samlData = $self->replayProtection($samlID);
unless ( $samlData && $samlData ne 1 ) {
$self->lmLog( "No logout dump found for samlID $samlID", 'error' );
2010-05-04 12:10:34 +02:00
return PE_IMG_NOK;
}
# Rebuild the logout object
my $logout;
unless ( $logout = $self->createLogout($server) ) {
2010-05-04 12:10:34 +02:00
$self->lmLog( "Could not rebuild logout object", 'error' );
return PE_IMG_NOK;
}
# Load Session and Identity if they exist
my $session = $samlData->{_lassoSessionDump};
my $identity = $samlData->{_lassoIdentityDump};
my $providerID = $samlData->{_providerID};
if ($session) {
unless ( $self->setSessionFromDump( $logout, $session ) ) {
$self->lmLog( "Unable to load Lasso Session", 'error' );
2010-05-04 12:10:34 +02:00
return PE_IMG_NOK;
}
$self->lmLog( "Lasso Session loaded", 'debug' );
}
if ($identity) {
unless ( $self->setIdentityFromDump( $logout, $identity ) ) {
$self->lmLog( "Unable to load Lasso Identity", 'error' );
2010-05-04 12:10:34 +02:00
return PE_IMG_NOK;
}
$self->lmLog( "Lasso Identity loaded", 'debug' );
}
# Send the logout request
my ( $rstatus, $rmethod, $rinfo ) =
2010-05-04 12:10:34 +02:00
$self->sendLogoutRequestToServiceProvider( $logout, $providerID,
Lasso::Constants::HTTP_METHOD_SOAP );
unless ($rstatus) {
$self->lmLog( "Fail to process SOAP logout request to $providerID",
'error' );
return PE_IMG_NOK;
}
return PE_IMG_OK;
}
# 1.4. Artifacts
if ( $url =~ /^(\Q$saml_ars_url\E)$/io ) {
2010-04-08 13:03:53 +02:00
$self->lmLog( "URL $url detected as an artifact resolution service URL",
2010-04-08 13:03:53 +02:00
'debug' );
# Artifact request are sent with SOAP trough POST
my $art_request = $self->param('POSTDATA');
my $art_response;
# Create Login object
my $login = $self->createLogin($server);
# Process request message
unless ( $self->processArtRequestMsg( $login, $art_request ) ) {
$self->lmLog( "Unable to process artifact request message",
'error' );
return PE_ERROR;
}
# Check Destination
return PE_ERROR
unless ( $self->checkDestination( $login->request, $url ) );
# Create artifact response
unless ( $art_response = $self->createArtifactResponse($login) ) {
$self->lmLog( "Unable to create artifact response message",
'error' );
return PE_ERROR;
}
$self->{SOAPMessage} = $art_response;
$self->lmLog( "Send SOAP Message: " . $self->{SOAPMessage}, 'debug' );
# Return SOAP message
$self->returnSOAPMessage();
2010-04-12 10:26:18 +02:00
# If we are here, there was a problem with SOAP request
$self->lmLog( "Artifact response was not sent trough SOAP", 'error' );
return PE_ERROR;
2010-04-08 13:03:53 +02:00
}
2010-05-04 12:10:34 +02:00
# 1.5 Attribute query
# TODO
2009-04-07 22:38:24 +02:00
PE_OK;
}
## @apmethod int issuerForAuthUser()
2009-04-07 22:38:24 +02:00
# Check if there is an SAML authentication request for an authenticated user
2010-04-07 14:27:50 +02:00
# Build assertions and redirect user
2009-04-07 22:38:24 +02:00
# @return Lemonldap::NG::Portal error code
sub issuerForAuthUser {
my $self = shift;
my $server = $self->{_lassoServer};
2010-04-07 14:27:50 +02:00
my $login;
my $protocolProfile;
my $artifact_method;
2010-04-07 18:37:23 +02:00
my $authn_context;
# Session ID
my $session_id = $self->{sessionInfo}->{_session_id} || $self->{id};
# Get configuration parameter
my $saml_sso_soap_url =
$self->getMetaDataURL( "samlIDPSSODescriptorSingleSignOnServiceSOAP", 1 );
my $saml_sso_soap_url_ret =
$self->getMetaDataURL( "samlIDPSSODescriptorSingleSignOnServiceSOAP", 2 );
my $saml_sso_get_url =
$self->getMetaDataURL( "samlIDPSSODescriptorSingleSignOnServiceHTTP", 1 );
my $saml_sso_get_url_ret =
$self->getMetaDataURL( "samlIDPSSODescriptorSingleSignOnServiceHTTP", 2 );
my $saml_slo_soap_url =
$self->getMetaDataURL( "samlIDPSSODescriptorSingleLogoutServiceSOAP", 1 );
my $saml_slo_soap_url_ret =
$self->getMetaDataURL( "samlIDPSSODescriptorSingleLogoutServiceSOAP", 2 );
my $saml_slo_get_url =
$self->getMetaDataURL( "samlIDPSSODescriptorSingleLogoutServiceHTTP", 1 );
my $saml_slo_get_url_ret =
$self->getMetaDataURL( "samlIDPSSODescriptorSingleLogoutServiceHTTP", 2 );
# Get HTTP request informations to know
# if we are receving SAML request or response
my $url = $self->url();
my $request_method = $self->request_method();
my $content_type = $self->content_type();
# 1.1. SSO
if ( $url =~ /^(\Q$saml_sso_soap_url\E|\Q$saml_sso_get_url\E)$/io ) {
$self->lmLog( "URL $url detected as an SSO request URL", 'debug' );
# Check message
my ( $request, $response, $method, $relaystate, $artifact ) =
$self->checkMessage( $url, $request_method, $content_type );
2010-03-26 14:56:37 +01:00
2010-05-04 12:10:34 +02:00
# Create Login object
my $login = $self->createLogin($server);
# Ignore signature verification
$self->disableSignatureVerification($login);
# Process the request
if ($request) {
# Load Session and Identity if they exist
my $session = $self->{sessionInfo}->{_lassoSessionDump};
my $identity = $self->{sessionInfo}->{_lassoIdentityDump};
if ($session) {
unless ( $self->setSessionFromDump( $login, $session ) ) {
$self->lmLog( "Unable to load Lasso Session", 'error' );
return PE_ERROR;
}
$self->lmLog( "Lasso Session loaded", 'debug' );
}
if ($identity) {
unless ( $self->setIdentityFromDump( $login, $identity ) ) {
$self->lmLog( "Unable to load Lasso Identity", 'error' );
return PE_ERROR;
}
$self->lmLog( "Lasso Identity loaded", 'debug' );
}
# Process authentication request
my $result;
if ($artifact) {
$result = $self->processArtRequestMsg( $login, $request );
}
else {
$result = $self->processAuthnRequestMsg( $login, $request );
}
unless ($result) {
$self->lmLog( "SSO: Fail to process authentication request",
'error' );
return PE_ERROR;
}
2010-05-04 12:10:34 +02:00
# Get SP entityID
my $sp = $login->remote_providerID();
2010-05-04 12:10:34 +02:00
$self->lmLog( "Found entityID $sp in SAML message", 'debug' );
2010-05-04 12:10:34 +02:00
# SP conf key
my $spConfKey = $self->{_spList}->{$sp}->{confKey};
unless ($spConfKey) {
$self->lmLog( "$sp do not match any SP in configuration",
'error' );
return PE_ERROR;
}
2010-05-04 12:10:34 +02:00
$self->lmLog( "$sp match $spConfKey SP in configuration", 'debug' );
2010-05-04 12:10:34 +02:00
# Do we check signature?
my $checkSSOMessageSignature =
$self->{samlSPMetaDataOptions}->{$spConfKey}
->{samlSPMetaDataOptionsCheckSSOMessageSignature};
if ($checkSSOMessageSignature) {
unless ( $self->checkSignatureStatus($login) ) {
$self->lmLog( "Signature is not valid", 'error' );
return PE_ERROR;
}
else {
$self->lmLog( "Signature is valid", 'debug' );
}
}
else {
$self->lmLog( "Message signature will not be checked",
'debug' );
}
2010-04-07 14:27:50 +02:00
# Validate request
unless ( $self->validateRequestMsg( $login, 1, 1 ) ) {
$self->lmLog( "Unable to validate SSO request message",
'error' );
return PE_ERROR;
}
2010-04-07 14:27:50 +02:00
$self->lmLog( "SSO: authentication request is valid", 'debug' );
# TODO Check AuthnRequest conditions
# Check Destination
return PE_ERROR
unless ( $self->checkDestination( $login->request, $url ) );
# Map authenticationLevel with SAML2 authentication context
my $authenticationLevel =
$self->{sessionInfo}->{authenticationLevel};
2010-04-07 18:37:23 +02:00
$authn_context =
$self->authnLevel2authnContext($authenticationLevel);
2010-04-07 18:37:23 +02:00
$self->lmLog( "Authentication context is $authn_context", 'debug' );
# Build Assertion
unless ( $self->buildAssertion( $login, $authn_context ) ) {
$self->lmLog( "Unable to build assertion", 'error' );
return PE_ERROR;
}
$self->lmLog( "SSO: assertion is built", 'debug' );
2010-04-07 14:27:50 +02:00
# Get default NameID Format from configuration
# Set to "email" if no value in configuration
my $nameIDFormatKey =
$self->{samlSPMetaDataOptions}->{$spConfKey}
->{samlSPMetaDataOptionsNameIDFormat} || "email";
my $nameIDFormat = $self->getNameIDFormat($nameIDFormatKey);
$self->lmLog( "Default NameID format is $nameIDFormat", 'debug' );
# Check NameID Policy in request
if ( $login->request()->NameIDPolicy ) {
$nameIDFormat = $login->request()->NameIDPolicy->Format();
}
# Get session key associated with NameIDFormat
# Not for unspecified, transient, persistent, entity, encrypted
my $nameIDFormatConfiguration = {
$self->getNameIDFormat("email") => 'samlNameIDFormatMapEmail',
$self->getNameIDFormat("x509") => 'samlNameIDFormatMapX509',
$self->getNameIDFormat("windows") =>
'samlNameIDFormatMapWindows',
$self->getNameIDFormat("kerberos") =>
'samlNameIDFormatMapKerberos',
};
my $nameIDSessionKey =
$self->{ $nameIDFormatConfiguration->{$nameIDFormat} };
my $nameIDContent;
if ( defined $self->{sessionInfo}->{$nameIDSessionKey} ) {
$nameIDContent =
$self->getFirstValue(
$self->{sessionInfo}->{$nameIDSessionKey} );
}
# Manage Entity NameID format
if ( $nameIDFormat eq $self->getNameIDFormat("entity") ) {
$nameIDContent = $self->{samlEntityID};
}
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->lmLog( "NameID Format is " . $login->nameIdentifier->Format,
'debug' );
$self->lmLog(
"NameID Content is " . $login->nameIdentifier->content,
'debug' );
# Push mandatory attributes
my @attributes;
foreach (
2010-05-04 12:10:34 +02:00
keys
%{ $self->{samlSPMetaDataExportedAttributes}->{$spConfKey} } )
{
# Extract fields from exportedAttr value
my ( $mandatory, $name, $format, $friendly_name ) =
split( /;/,
2010-05-04 12:10:34 +02:00
$self->{samlSPMetaDataExportedAttributes}->{$spConfKey}
->{$_} );
# Name is required
next unless $name;
# Do not send attribute if not mandatory
unless ($mandatory) {
$self->lmLog( "SAML2 attribute $name is not mandatory",
'debug' );
next;
}
# Error if corresponding attribute is not in user session
my $value = $self->{sessionInfo}->{$_};
unless ( defined $value ) {
$self->lmLog(
"Session key $_ is required to set SAML $name attribute",
'error'
);
return PE_ERROR;
}
$self->lmLog(
"SAML2 attribute $name will be set with $_ session key",
'debug' );
# SAML2 attribute
my $attribute;
eval { $attribute = Lasso::Saml2Attribute->new(); };
if ($@) {
$self->checkLassoError($@);
return PE_ERROR;
}
# Default values
$friendly_name ||= $name;
$format ||= Lasso::Constants::SAML2_ATTRIBUTE_NAME_FORMAT_BASIC;
# Set attribute properties
$attribute->Name($name);
$attribute->NameFormat($format);
$attribute->FriendlyName($friendly_name);
# Set attribute value(s)
my @values = split $self->{multiValuesSeparator}, $value;
my @saml2values;
foreach (@values) {
# SAML2 attribute value
my $saml2value;
eval { $saml2value = Lasso::Saml2AttributeValue->new(); };
if ($@) {
$self->checkLassoError($@);
return PE_ERROR;
}
my @any;
my $textNode;
eval { $textNode = Lasso::MiscTextNode->new(); };
if ($@) {
$self->checkLassoError($@);
return PE_ERROR;
}
$textNode->text_child(1);
$textNode->content($_);
push @any, $textNode;
$saml2value->any(@any);
push @saml2values, $saml2value;
$self->lmLog( "Push $_ in SAML attribute $name", 'debug' );
}
$attribute->AttributeValue(@saml2values);
# Push attribute in attribute list
push @attributes, $attribute;
}
2010-04-07 14:27:50 +02:00
# Create attribute statement
my $attribute_statement;
eval {
$attribute_statement = Lasso::Saml2AttributeStatement->new();
};
if ($@) {
$self->checkLassoError($@);
return PE_ERROR;
}
# Register attributes in attribute statement
$attribute_statement->Attribute(@attributes);
# Get response assertion
my @response_assertions = $login->response->Assertion;
unless ( $response_assertions[0] ) {
$self->lmLog( "Unable to get response assertion", 'error' );
return PE_ERROR;
}
# Set subject NameID
$response_assertions[0]
->set_subject_name_id( $login->nameIdentifier );
# Add attribute statement in response assertion
my @attributes_statement = ($attribute_statement);
$response_assertions[0]->AttributeStatement(@attributes_statement);
# Set sessionIndex
# sessionIndex is the encrypted session_id
my $sessionIndex = $self->{cipher}->encrypt($session_id);
my @authn_statements = $response_assertions[0]->AuthnStatement();
$authn_statements[0]->SessionIndex($sessionIndex);
$response_assertions[0]->AuthnStatement(@authn_statements);
$self->lmLog(
"Set sessionIndex $sessionIndex (encrypted from $session_id)",
'debug' );
# Set response assertion
$login->response->Assertion(@response_assertions);
# Signature
my $signSSOMessage =
$self->{samlSPMetaDataOptions}->{$spConfKey}
->{samlSPMetaDataOptionsSignSSOMessage};
unless ($signSSOMessage) {
$self->lmLog( "Do not sign this SSO response", 'debug' );
return PE_ERROR unless ( $self->disableSignature($login) );
}
# Build SAML response
$protocolProfile = $login->protocolProfile();
# Artifact
if ( $protocolProfile ==
Lasso::Constants::LOGIN_PROTOCOL_PROFILE_BRWS_ART )
{
# Choose method
$artifact_method = $self->getHttpMethod("artifact-get")
if ( $method == $self->getHttpMethod("redirect") );
$artifact_method = $self->getHttpMethod("artifact-post")
if ( $method == $self->getHttpMethod("post") );
# Build artifact message
unless ( $self->buildArtifactMsg( $login, $artifact_method ) ) {
$self->lmLog(
"Unable to build SSO artifact response message",
'error' );
return PE_ERROR;
}
$self->lmLog( "SSO: artifact response is built", 'debug' );
# Get artifact ID and Content, and store them
2010-04-12 10:26:18 +02:00
my $artifact_id = $login->get_artifact;
my $artifact_message = $login->get_artifact_message;
2010-04-07 14:27:50 +02:00
$self->storeArtifact( $artifact_id, $artifact_message,
$session_id );
}
# No artifact
else {
unless ( $self->buildAuthnResponseMsg($login) ) {
$self->lmLog( "Unable to build SSO response message",
'error' );
return PE_ERROR;
}
2010-03-26 14:56:37 +01:00
$self->lmLog( "SSO: authentication response is built",
'debug' );
}
# Save Identity and Session
if ( $login->is_identity_dirty ) {
$self->lmLog( "Save Lasso identity in session", 'debug' );
$self->updateSession(
{ _lassoIdentityDump => $login->get_identity->dump },
$session_id );
}
if ( $login->is_session_dirty ) {
$self->lmLog( "Save Lasso session in session", 'debug' );
$self->updateSession(
{ _lassoSessionDump => $login->get_session->dump },
$session_id );
}
# Send SSO Response
# HTTP-REDIRECT
if ( $protocolProfile eq
Lasso::Constants::LOGIN_PROTOCOL_PROFILE_REDIRECT
or $artifact_method == $self->getHttpMethod("artifact-get") )
{
# Redirect user to response URL
my $sso_url = $login->msg_url;
$self->lmLog( "Redirect user to $sso_url", 'debug' );
$self->{urldc} = $sso_url;
$self->_subProcess(qw(autoRedirect));
# If we are here, there was a problem with GET request
$self->lmLog( "SSO response was not sent trough GET", 'error' );
return PE_ERROR;
}
# HTTP-POST
if ( $protocolProfile eq
Lasso::Constants::LOGIN_PROTOCOL_PROFILE_BRWS_POST
or $artifact_method == $self->getHttpMethod("artifact-post") )
{
# Use autosubmit form
my $sso_url = $login->msg_url;
my $sso_body = $login->msg_body;
$self->{postUrl} = $sso_url;
$self->{postFields} = { 'SAMLResponse' => $sso_body };
# RelayState
$self->{postFields}->{'RelayState'} = $login->msg_relayState
if ( $login->msg_relayState );
$self->_subProcess(qw(autoPost));
# If we are here, there was a problem with POST request
$self->lmLog( "SSO response was not sent trough POST",
'error' );
return PE_ERROR;
}
}
elsif ($response) {
$self->lmLog(
"Authentication responses are not managed by this module",
'debug' );
return PE_OK;
}
else {
# No request or response
# This should not happen
$self->lmLog( "No request or response found", 'debug' );
return PE_OK;
}
}
# 1.2. SLO
if ( $url =~
/^(\Q$saml_slo_soap_url\E|\Q$saml_slo_soap_url_ret\E|\Q$saml_slo_get_url\E|\Q$saml_slo_get_url_ret\E)$/io
)
{
$self->lmLog( "URL $url detected as an SLO URL", 'debug' );
# Check SAML Message
my ( $request, $response, $method, $relaystate, $artifact ) =
$self->checkMessage( $url, $request_method, $content_type, "logout" );
2010-05-04 12:10:34 +02:00
# Create Logout object
my $logout = $self->createLogout($server);
# Ignore signature verification
$self->disableSignatureVerification($logout);
if ($request) {
# Load Session and Identity if they exist
my $session = $self->{sessionInfo}->{_lassoSessionDump};
my $identity = $self->{sessionInfo}->{_lassoIdentityDump};
if ($session) {
unless ( $self->setSessionFromDump( $logout, $session ) ) {
$self->lmLog( "Unable to load Lasso Session", 'error' );
return PE_ERROR;
}
$self->lmLog( "Lasso Session loaded", 'debug' );
}
if ($identity) {
unless ( $self->setIdentityFromDump( $logout, $identity ) ) {
$self->lmLog( "Unable to load Lasso Identity", 'error' );
return PE_ERROR;
}
$self->lmLog( "Lasso Identity loaded", 'debug' );
}
# Process logout request
unless ( $self->processLogoutRequestMsg( $logout, $request ) ) {
$self->lmLog( "SLO: Fail to process logout request", 'error' );
return PE_ERROR;
}
$self->lmLog( "SLO: Logout request is valid", 'debug' );
2010-05-04 12:10:34 +02:00
# Get SP entityID
my $sp = $logout->remote_providerID();
$self->lmLog( "Found entityID $sp in SAML message", 'debug' );
# SP conf key
my $spConfKey = $self->{_spList}->{$sp}->{confKey};
unless ($spConfKey) {
$self->lmLog( "$sp do not match any SP in configuration",
'error' );
return PE_ERROR;
}
$self->lmLog( "$sp match $spConfKey SP in configuration", 'debug' );
# Do we check signature?
my $checkSLOMessageSignature =
$self->{samlSPMetaDataOptions}->{$spConfKey}
->{samlSPMetaDataOptionsCheckSLOMessageSignature};
if ($checkSLOMessageSignature) {
unless ( $self->checkSignatureStatus($logout) ) {
$self->lmLog( "Signature is not valid", 'error' );
return PE_ERROR;
}
else {
$self->lmLog( "Signature is valid", 'debug' );
}
}
else {
$self->lmLog( "Message signature will not be checked",
'debug' );
}
2010-05-04 12:10:34 +02:00
# Check Destination
return PE_ERROR
unless ( $self->checkDestination( $logout->request, $url ) );
# Get session index
my $session_index;
eval { $session_index = $logout->request()->SessionIndex; };
# Proceed to logout on all others SP
my $logout_dump = $logout->dump;
my $provider_nb =
2010-04-26 17:39:38 +02:00
$self->sendLogoutRequestToServiceProviders($logout);
# Rebuild Lasso::Logout object. All data have already been checked.
$logout = Lasso::Logout::new_from_dump( $server, $logout_dump );
$self->setSessionFromDump( $logout, $session );
$self->setIdentityFromDump( $logout, $identity );
# Validate request if no previous error
unless ( $self->validateLogoutRequest($logout) ) {
$self->lmLog( "SLO request is not valid", 'error' );
2010-04-27 17:11:53 +02:00
return PE_ERROR;
}
# Set RelayState
if ($relaystate) {
$logout->msg_relayState($relaystate);
$self->lmLog( "Set $relaystate in RelayState", 'debug' );
}
# SLO requests without session index are not accepted
if ( $@ or !defined $session_index ) {
$self->lmLog(
"No session index in SLO request from $spConfKey SP",
'error' );
return PE_ERROR;
}
# Decrypt session index
my $local_session_id = $self->{cipher}->decrypt($session_index);
$self->lmLog(
"Get session id $local_session_id (decrypted from $session_index)",
'debug'
);
my $user = $self->{sessionInfo}->{user};
my $local_session = $self->getApacheSession( $local_session_id, 1 );
unless ( $self->_deleteSession($local_session) ) {
$self->lmLog(
"Fail to delete session $local_session_id for user $user",
'debug' );
}
# Signature
my $signSLOMessage =
$self->{samlSPMetaDataOptions}->{$spConfKey}
->{samlSPMetaDataOptionsSignSLOMessage};
unless ($signSLOMessage) {
$self->lmLog( "Do not sign this SLO response", 'debug' );
return PE_ERROR unless ( $self->disableSignature($logout) );
}
# Send logout response. The process could be stopped here, if no
# there are no providers to wait for logout via HTTP-REDIRECT
# method.
my $status =
$self->sendLogoutResponseToServiceProvider( $logout, $method,
$relaystate, $provider_nb );
# Verify that logout response is correctly sent. If we have to wait
# for providers during HTTP-REDIRECT process, return PE_INFO to
# notify to wait for them.
if ( $provider_nb && $status ) {
return PE_INFO;
}
elsif ( !$status ) {
return PE_ERROR;
}
}
}
return PE_OK;
2009-04-07 22:38:24 +02:00
}
## @apmethod int issuerLogout()
2010-05-04 12:10:34 +02:00
# Send logout to SP when logout is initiated by IDP
# @return Lemonldap::NG::Portal error code
sub issuerLogout {
2009-04-08 18:31:13 +02:00
my $self = shift;
2010-03-26 14:56:37 +01:00
2010-04-27 17:11:53 +02:00
# Create Logout object
my $logout = $self->createLogout( $self->{_lassoServer} );
2010-04-27 17:11:53 +02:00
# Load Session and Identity if they exist
my $session = $self->{sessionInfo}->{_lassoSessionDump};
my $identity = $self->{sessionInfo}->{_lassoIdentityDump};
if ($session) {
unless ( $self->setSessionFromDump( $logout, $session ) ) {
$self->lmLog( "Unable to load Lasso Session", 'error' );
return PE_ERROR;
}
$self->lmLog( "Lasso Session loaded", 'debug' );
}
# No need to initiate logout requests on SP, if no SAML session is
# available into the session.
else {
return PE_OK;
}
if ($identity) {
unless ( $self->setIdentityFromDump( $logout, $identity ) ) {
$self->lmLog( "Unable to load Lasso Identity", 'error' );
return PE_ERROR;
}
$self->lmLog( "Lasso Identity loaded", 'debug' );
}
# Proceed to logout on all others SP.
# Verify that logout response is correctly sent. If we have to wait for
# providers during HTTP-REDIRECT process, return PE_INFO to notify to wait
# for them.
if ( $self->sendLogoutRequestToServiceProviders($logout) ) {
return PE_INFO;
}
return PE_OK;
2009-04-08 18:31:13 +02:00
}
2009-04-07 22:38:24 +02:00
1;
2009-04-07 22:38:24 +02:00
__END__
=head1 NAME
=encoding utf8
2010-04-07 14:27:50 +02:00
Lemonldap::NG::Portal::IssuerDBSAML - SAML IssuerDB for LemonLDAP::NG
2009-04-07 22:38:24 +02:00
=head1 SYNOPSIS
2010-04-07 14:27:50 +02:00
use Lemonldap::NG::Portal::SharedConf;
my $portal = Lemonldap::NG::Portal::SharedConf->new({
issuerDB => SAML,
});
2009-04-07 22:38:24 +02:00
=head1 DESCRIPTION
2010-04-07 14:27:50 +02:00
SAML IssuerDB for LemonLDAP::NG
2009-04-07 22:38:24 +02:00
=head1 SEE ALSO
L<Lemonldap::NG::Portal>
=head1 AUTHOR
Clément Oudot, E<lt>coudot@linagora.comE<gt>
2009-04-07 22:38:24 +02:00
=head1 COPYRIGHT AND LICENSE
Copyright (C) 2009 by Clément Oudot
2009-04-07 22:38:24 +02:00
This library is free software; you can redistribute it and/or modify
it under the same terms as Perl itself, either Perl version 5.10.0 or,
at your option, any later version of Perl 5 you may have available.
=cut