2016-09-19 23:00:27 +02:00
|
|
|
package Lemonldap::NG::Portal::Lib::SAML;
|
|
|
|
|
|
|
|
use strict;
|
|
|
|
use Mouse;
|
|
|
|
use Lemonldap::NG::Common::Conf::SAML::Metadata;
|
|
|
|
use Lemonldap::NG::Common::Session;
|
2017-02-15 16:08:23 +01:00
|
|
|
use Lemonldap::NG::Common::UserAgent;
|
2018-06-26 19:13:06 +02:00
|
|
|
use Lemonldap::NG::Common::FormEncode;
|
2016-09-19 23:00:27 +02:00
|
|
|
use XML::Simple;
|
2019-07-25 14:55:15 +02:00
|
|
|
use HTML::Entities qw(decode_entities);
|
2016-09-19 23:00:27 +02:00
|
|
|
use MIME::Base64;
|
|
|
|
use HTTP::Request; # SOAP call
|
|
|
|
use POSIX qw(strftime); # Convert SAML2 date into timestamp
|
|
|
|
use Time::Local; # Convert SAML2 date into timestamp
|
|
|
|
use Encode; # Encode attribute values
|
|
|
|
use URI; # Get metadata URL path
|
2016-12-09 11:25:05 +01:00
|
|
|
use Lemonldap::NG::Portal::Main::Constants qw(
|
|
|
|
PE_OK
|
|
|
|
PE_REDIRECT
|
2019-11-06 18:38:58 +01:00
|
|
|
PE_LOGOUT_OK
|
2016-12-09 11:25:05 +01:00
|
|
|
PE_SAML_SLO_ERROR
|
|
|
|
);
|
2016-09-19 23:00:27 +02:00
|
|
|
|
2020-09-04 17:59:00 +02:00
|
|
|
our $VERSION = '2.0.9';
|
2016-09-19 23:00:27 +02:00
|
|
|
|
|
|
|
# PROPERTIES
|
|
|
|
|
2016-09-21 22:08:50 +02:00
|
|
|
has lassoServer => ( is => 'rw' );
|
|
|
|
has spList => ( is => 'rw', default => sub { {} } );
|
|
|
|
has idpList => ( is => 'rw', default => sub { {} } );
|
2017-03-22 20:03:49 +01:00
|
|
|
has idpRules => ( is => 'rw', default => sub { {} } );
|
2017-03-22 21:20:30 +01:00
|
|
|
has spRules => ( is => 'rw', default => sub { {} } );
|
2019-10-22 17:07:17 +02:00
|
|
|
has spMacros => ( is => 'rw', default => sub { {} } );
|
2016-09-19 23:00:27 +02:00
|
|
|
|
2016-11-29 22:10:00 +01:00
|
|
|
# return LWP::UserAgent object
|
|
|
|
has ua => (
|
|
|
|
is => 'rw',
|
2016-11-29 22:58:28 +01:00
|
|
|
lazy => 1,
|
2016-11-29 22:10:00 +01:00
|
|
|
builder => sub {
|
|
|
|
|
|
|
|
# TODO : LWP options to use a proxy for example
|
2017-02-15 16:08:23 +01:00
|
|
|
my $ua = Lemonldap::NG::Common::UserAgent->new( $_[0]->{conf} );
|
2016-11-29 22:10:00 +01:00
|
|
|
$ua->env_proxy();
|
|
|
|
return $ua;
|
|
|
|
}
|
|
|
|
);
|
|
|
|
|
2017-09-27 07:00:00 +02:00
|
|
|
# SAML storage
|
|
|
|
has aModule => ( is => 'rw' );
|
|
|
|
has amOpts => ( is => 'rw' );
|
|
|
|
|
2016-09-19 23:00:27 +02:00
|
|
|
# INITIALIZATION
|
|
|
|
|
|
|
|
BEGIN {
|
|
|
|
|
|
|
|
# Load Glib if available
|
|
|
|
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";
|
|
|
|
}
|
|
|
|
|
|
|
|
# Load Lasso.pm
|
|
|
|
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';
|
|
|
|
|
|
|
|
# Check Lasso version >= 2.3.0
|
|
|
|
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';
|
|
|
|
}
|
|
|
|
|
|
|
|
# Try to set thin-sessions flag
|
|
|
|
eval 'Lasso::set_flag("thin-sessions");';
|
|
|
|
if ($@) {
|
|
|
|
eval 'use constant LASSOTHINSESSIONS => 0';
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
eval 'use constant LASSOTHINSESSIONS => 1';
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
sub init {
|
|
|
|
my ($self) = @_;
|
|
|
|
|
2017-09-27 07:00:00 +02:00
|
|
|
# Initialize SAML storage
|
|
|
|
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);
|
|
|
|
|
2016-09-19 23:00:27 +02:00
|
|
|
# Check for Lasso errors/messages (see BEGIN)
|
|
|
|
unless (LASSO) {
|
2018-09-11 11:07:23 +02:00
|
|
|
$self->logger->error("Module Lasso not loaded (see below)");
|
2016-09-19 23:00:27 +02:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (BADLASSO) {
|
2018-09-11 11:07:23 +02:00
|
|
|
$self->logger->error('Lasso version >= 2.3.0 required');
|
2016-09-19 23:00:27 +02:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
unless (LASSOTHINSESSIONS) {
|
2017-02-15 07:41:50 +01:00
|
|
|
$self->logger->warn('Lasso thin-sessions flag could not be set');
|
2016-09-19 23:00:27 +02:00
|
|
|
}
|
|
|
|
else {
|
2017-02-15 07:41:50 +01:00
|
|
|
$self->logger->debug('Lasso thin-sessions flag set');
|
2016-09-19 23:00:27 +02:00
|
|
|
}
|
2016-11-14 22:45:32 +01:00
|
|
|
if (GLIB) {
|
|
|
|
Glib::Log->set_handler(
|
|
|
|
"Lasso",
|
|
|
|
[qw/ error critical warning message info debug /],
|
|
|
|
sub {
|
2017-02-15 07:41:50 +01:00
|
|
|
$self->logger->debug(
|
|
|
|
$_[0] . " error " . $_[1] . ": " . $_[2] );
|
2016-11-14 22:45:32 +01:00
|
|
|
}
|
|
|
|
);
|
|
|
|
}
|
2016-09-19 23:00:27 +02:00
|
|
|
|
|
|
|
# Conf initialization
|
|
|
|
|
2016-09-21 22:08:50 +02:00
|
|
|
return 0 unless ( $self->lassoServer( $self->loadService ) );
|
2016-12-28 19:38:03 +01:00
|
|
|
$self->addUnauthRoute(
|
2019-03-07 18:22:16 +01:00
|
|
|
( $self->{path} || 'saml' ) =>
|
|
|
|
{ 'metadata' => { ':type' => 'metadata' } },
|
2016-12-28 19:38:03 +01:00
|
|
|
['GET']
|
|
|
|
);
|
|
|
|
$self->addAuthRoute(
|
2019-03-07 18:22:16 +01:00
|
|
|
( $self->{path} || 'saml' ) =>
|
|
|
|
{ 'metadata' => { ':type' => 'metadata' } },
|
2016-12-28 19:38:03 +01:00
|
|
|
['GET']
|
|
|
|
);
|
|
|
|
return 1;
|
2016-09-21 22:08:50 +02:00
|
|
|
}
|
2016-09-19 23:00:27 +02:00
|
|
|
|
2016-12-29 11:12:08 +01:00
|
|
|
# METHODS
|
|
|
|
|
2016-09-21 22:08:50 +02:00
|
|
|
sub loadService {
|
|
|
|
my ($self) = @_;
|
|
|
|
|
|
|
|
# Check if certificate is available
|
2016-09-19 23:00:27 +02:00
|
|
|
unless ($self->conf->{samlServicePublicKeySig}
|
|
|
|
and $self->conf->{samlServicePrivateKeySig} )
|
|
|
|
{
|
2018-09-11 11:07:23 +02:00
|
|
|
$self->logger->error(
|
|
|
|
'SAML private and public key not found in configuration');
|
2016-09-19 23:00:27 +02:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
my $serviceCertificate;
|
|
|
|
if ( $self->conf->{samlServiceUseCertificateInResponse}
|
|
|
|
and $self->conf->{samlServicePublicKeySig} =~ /CERTIFICATE/ )
|
|
|
|
{
|
|
|
|
$serviceCertificate = $self->conf->{samlServicePublicKeySig};
|
2017-02-15 07:41:50 +01:00
|
|
|
$self->logger->debug('Certificate will be used in SAML responses');
|
2016-09-19 23:00:27 +02:00
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
# Get metadata from configuration
|
2017-02-15 07:41:50 +01:00
|
|
|
$self->logger->debug("Get Metadata for this service");
|
2016-09-19 23:00:27 +02:00
|
|
|
my $service_metadata = Lemonldap::NG::Common::Conf::SAML::Metadata->new();
|
|
|
|
|
|
|
|
# Create Lasso server with service metadata
|
|
|
|
my $server = $self->createServer(
|
2019-04-10 17:21:33 +02:00
|
|
|
$service_metadata->serviceToXML( $self->conf, '' ),
|
2016-09-19 23:00:27 +02:00
|
|
|
$self->conf->{samlServicePrivateKeySig},
|
|
|
|
$self->conf->{samlServicePrivateKeySigPwd},
|
2016-09-21 22:08:50 +02:00
|
|
|
|
|
|
|
# use signature cert for encryption unless defined
|
|
|
|
(
|
|
|
|
$self->conf->{samlServicePrivateKeyEnc}
|
|
|
|
? (
|
|
|
|
$self->conf->{samlServicePrivateKeyEnc},
|
|
|
|
$self->conf->{samlServicePrivateKeyEncPwd}
|
|
|
|
)
|
|
|
|
: (
|
|
|
|
$self->conf->{samlServicePrivateKeySig},
|
|
|
|
$self->conf->{samlServicePrivateKeySigPwd}
|
|
|
|
)
|
|
|
|
),
|
2016-09-19 23:00:27 +02:00
|
|
|
$serviceCertificate
|
|
|
|
);
|
|
|
|
|
2017-08-29 18:43:43 +02:00
|
|
|
# Signature method
|
2018-06-12 18:22:21 +02:00
|
|
|
my $method = $self->conf->{samlServiceSignatureMethod} || 'RSA_SHA1';
|
2017-08-29 18:43:43 +02:00
|
|
|
$server->signature_method( $self->getSignatureMethod($method) );
|
|
|
|
$self->logger->debug("Set $method as SAML server signature method ");
|
|
|
|
|
2016-09-19 23:00:27 +02:00
|
|
|
# Log
|
|
|
|
unless ($server) {
|
2018-09-11 11:07:23 +02:00
|
|
|
$self->logger->error('Unable to create Lasso server');
|
2016-09-19 23:00:27 +02:00
|
|
|
return 0;
|
|
|
|
}
|
2017-02-15 07:41:50 +01:00
|
|
|
$self->logger->debug("Service created");
|
2016-09-19 23:00:27 +02:00
|
|
|
|
|
|
|
return $server;
|
|
|
|
}
|
|
|
|
|
2016-09-21 22:08:50 +02:00
|
|
|
sub loadIDPs {
|
|
|
|
my ($self) = @_;
|
|
|
|
|
|
|
|
# Check presence of at least one identity provider in configuration
|
|
|
|
unless ( $self->conf->{samlIDPMetaDataXML}
|
|
|
|
and keys %{ $self->conf->{samlIDPMetaDataXML} } )
|
|
|
|
{
|
2017-11-07 11:29:06 +01:00
|
|
|
$self->logger->debug("No IDP found in configuration");
|
2016-09-21 22:08:50 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
# Load identity provider metadata
|
|
|
|
# IDP metadata are listed in $self->{samlIDPMetaDataXML}
|
|
|
|
# Each key is the IDP name
|
|
|
|
# Build IDP list for later use in extractFormInfo
|
|
|
|
$self->idpList( {} );
|
|
|
|
|
|
|
|
foreach ( keys %{ $self->conf->{samlIDPMetaDataXML} } ) {
|
2017-02-15 07:41:50 +01:00
|
|
|
$self->logger->debug("Get Metadata for IDP $_");
|
2016-09-21 22:08:50 +02:00
|
|
|
|
|
|
|
my $idp_metadata =
|
|
|
|
$self->conf->{samlIDPMetaDataXML}->{$_}->{samlIDPMetaDataXML};
|
|
|
|
|
|
|
|
# Check metadata format
|
|
|
|
if ( ref $idp_metadata eq "HASH" ) {
|
2018-09-11 11:07:23 +02:00
|
|
|
$self->logger->error(
|
2016-09-21 22:08:50 +02:00
|
|
|
"Metadata for IDP $_ is in old format. Please reload them from Manager"
|
|
|
|
);
|
2018-10-09 19:36:59 +02:00
|
|
|
next;
|
2016-09-21 22:08:50 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if ( $self->conf->{samlMetadataForceUTF8} ) {
|
|
|
|
$idp_metadata = encode( "utf8", $idp_metadata );
|
|
|
|
}
|
|
|
|
|
|
|
|
# Add this IDP to Lasso::Server
|
2019-09-03 18:12:12 +02:00
|
|
|
# TODO: when Lasso issue #35061 is fixed in all distros,
|
|
|
|
# we could load the metadata into a new LassoProvider, extract the
|
|
|
|
# parsed-and-decoded entityID from this Provider, and then add the
|
|
|
|
# Provider into the server with $server->add_provider2, instead of
|
|
|
|
# decoding HTML entities ourselves below
|
2016-09-21 22:08:50 +02:00
|
|
|
my $result = $self->addIDP( $self->lassoServer, $idp_metadata );
|
|
|
|
|
|
|
|
unless ($result) {
|
2018-09-11 11:07:23 +02:00
|
|
|
$self->logger->error("Fail to use IDP $_ Metadata");
|
2018-10-09 19:36:59 +02:00
|
|
|
next;
|
2016-09-21 22:08:50 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
# Store IDP entityID and Organization Name
|
2017-03-11 08:27:52 +01:00
|
|
|
my ( $tmp, $entityID ) =
|
|
|
|
( $idp_metadata =~ /entityID=(['"])(.+?)\1/si );
|
2019-07-25 14:55:15 +02:00
|
|
|
|
|
|
|
# Decode HTML entities from entityID
|
2019-09-03 18:12:12 +02:00
|
|
|
# TODO: see Lasso comment above
|
2019-07-25 14:55:15 +02:00
|
|
|
decode_entities($entityID);
|
|
|
|
|
2016-09-21 22:08:50 +02:00
|
|
|
my $name = $self->getOrganizationName( $self->lassoServer, $entityID )
|
|
|
|
|| ucfirst($_);
|
|
|
|
$self->idpList->{$entityID}->{confKey} = $_;
|
|
|
|
$self->idpList->{$entityID}->{name} = $name;
|
|
|
|
|
|
|
|
# Set encryption mode
|
|
|
|
my $encryption_mode = $self->conf->{samlIDPMetaDataOptions}->{$_}
|
2020-03-12 17:00:50 +01:00
|
|
|
->{samlIDPMetaDataOptionsEncryptionMode} || "none";
|
2016-09-21 22:08:50 +02:00
|
|
|
my $lasso_encryption_mode = $self->getEncryptionMode($encryption_mode);
|
|
|
|
|
|
|
|
unless (
|
|
|
|
$self->setProviderEncryptionMode(
|
|
|
|
$self->lassoServer->get_provider($entityID),
|
|
|
|
$lasso_encryption_mode
|
|
|
|
)
|
|
|
|
)
|
|
|
|
{
|
2018-09-11 11:07:23 +02:00
|
|
|
$self->logger->error(
|
2016-09-21 22:08:50 +02:00
|
|
|
"Unable to set encryption mode $encryption_mode on IDP $_");
|
2018-10-09 19:36:59 +02:00
|
|
|
next;
|
2016-09-21 22:08:50 +02:00
|
|
|
}
|
2018-10-09 19:36:59 +02:00
|
|
|
$self->logger->debug("Set encryption mode $encryption_mode on IDP $_");
|
2016-09-21 22:08:50 +02:00
|
|
|
|
2019-09-28 00:18:16 +02:00
|
|
|
# Set signature method if overriden
|
|
|
|
my $signature_method = $self->conf->{samlIDPMetaDataOptions}->{$_}
|
|
|
|
->{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 $_"
|
|
|
|
);
|
|
|
|
next;
|
|
|
|
}
|
|
|
|
$self->logger->debug(
|
|
|
|
"Set signature method $signature_method on IDP $_");
|
|
|
|
}
|
|
|
|
|
2019-02-05 22:39:09 +01:00
|
|
|
# Set display options
|
2019-02-05 23:12:17 +01:00
|
|
|
$self->idpList->{$entityID}->{displayName} =
|
|
|
|
$self->conf->{samlIDPMetaDataOptions}->{$_}
|
|
|
|
->{samlIDPMetaDataOptionsDisplayName};
|
|
|
|
$self->idpList->{$entityID}->{icon} =
|
|
|
|
$self->conf->{samlIDPMetaDataOptions}->{$_}
|
|
|
|
->{samlIDPMetaDataOptionsIcon};
|
2019-04-10 16:14:49 +02:00
|
|
|
$self->idpList->{$entityID}->{order} =
|
|
|
|
$self->conf->{samlIDPMetaDataOptions}->{$_}
|
|
|
|
->{samlIDPMetaDataOptionsSortNumber};
|
2019-02-05 22:39:09 +01:00
|
|
|
|
|
|
|
# Set rule
|
2017-03-22 21:20:30 +01:00
|
|
|
my $cond = $self->conf->{samlIDPMetaDataOptions}->{$_}
|
|
|
|
->{samlIDPMetaDataOptionsResolutionRule};
|
|
|
|
if ( length $cond ) {
|
2017-03-22 20:03:49 +01:00
|
|
|
$cond = $self->p->HANDLER->substitute($cond);
|
2017-03-22 21:20:30 +01:00
|
|
|
unless ( $cond = $self->p->HANDLER->buildSub($cond) ) {
|
2018-09-11 11:07:23 +02:00
|
|
|
$self->logger->error( 'SAML IdP rule error: '
|
2017-03-22 21:20:30 +01:00
|
|
|
. $self->p->HANDLER->tsv->{jail}->error );
|
2018-10-09 19:36:59 +02:00
|
|
|
next;
|
2017-03-22 20:03:49 +01:00
|
|
|
}
|
|
|
|
$self->idpRules->{$entityID} = $cond;
|
|
|
|
}
|
2016-09-21 22:08:50 +02:00
|
|
|
|
2017-02-15 07:41:50 +01:00
|
|
|
$self->logger->debug("IDP $_ added");
|
2016-09-21 22:08:50 +02:00
|
|
|
}
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
sub loadSPs {
|
|
|
|
my ($self) = @_;
|
|
|
|
|
|
|
|
# Check presence of at least one service provider in configuration
|
|
|
|
unless ( $self->conf->{samlSPMetaDataXML}
|
|
|
|
and keys %{ $self->conf->{samlSPMetaDataXML} } )
|
|
|
|
{
|
2017-11-07 11:29:06 +01:00
|
|
|
$self->logger->debug("No SP found in configuration");
|
2016-09-21 22:08:50 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
# Load service provider metadata
|
|
|
|
# SP metadata are listed in $self->{samlSPMetaDataXML}
|
|
|
|
# Each key is the SP name
|
|
|
|
# Build SP list for later use in extractFormInfo
|
|
|
|
$self->spList( {} );
|
|
|
|
foreach ( keys %{ $self->conf->{samlSPMetaDataXML} } ) {
|
|
|
|
|
2017-02-15 07:41:50 +01:00
|
|
|
$self->logger->debug("Get Metadata for SP $_");
|
2016-09-21 22:08:50 +02:00
|
|
|
|
|
|
|
my $sp_metadata =
|
|
|
|
$self->conf->{samlSPMetaDataXML}->{$_}->{samlSPMetaDataXML};
|
|
|
|
|
|
|
|
# Check metadata format
|
|
|
|
if ( ref $sp_metadata eq "HASH" ) {
|
2018-09-11 11:07:23 +02:00
|
|
|
$self->logger->error(
|
2016-09-21 22:08:50 +02:00
|
|
|
"Metadata for SP $_ is in old format. Please reload them from Manager"
|
|
|
|
);
|
2018-10-09 19:36:59 +02:00
|
|
|
next;
|
2016-09-21 22:08:50 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if ( $self->conf->{samlMetadataForceUTF8} ) {
|
|
|
|
$sp_metadata = encode( "utf8", $sp_metadata );
|
|
|
|
}
|
|
|
|
|
|
|
|
# Add this SP to Lasso::Server
|
2019-09-03 18:12:12 +02:00
|
|
|
# TODO: when Lasso issue #35061 is fixed in all distros,
|
|
|
|
# we could load the metadata into a new LassoProvider, extract the
|
|
|
|
# parsed-and-decoded entityID from this Provider, and then add the
|
|
|
|
# Provider into the server with $server->add_provider2, instead of
|
|
|
|
# decoding HTML entities ourselves below
|
2016-09-21 22:08:50 +02:00
|
|
|
my $result = $self->addSP( $self->lassoServer, $sp_metadata );
|
|
|
|
|
|
|
|
unless ($result) {
|
2018-09-11 11:07:23 +02:00
|
|
|
$self->logger->error("Fail to use SP $_ Metadata");
|
2018-10-09 19:36:59 +02:00
|
|
|
next;
|
2016-09-21 22:08:50 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
# Store SP entityID and Organization Name
|
2017-03-22 21:20:30 +01:00
|
|
|
my ( $tmp, $entityID ) = ( $sp_metadata =~ /entityID=(['"])(.+?)\1/si );
|
2019-07-25 14:55:15 +02:00
|
|
|
|
|
|
|
# Decode HTML entities from entityID
|
2019-09-03 18:12:12 +02:00
|
|
|
# TODO: see Lasso comment above
|
2019-07-25 14:55:15 +02:00
|
|
|
decode_entities($entityID);
|
|
|
|
|
2016-09-21 22:08:50 +02:00
|
|
|
my $name = $self->getOrganizationName( $self->lassoServer, $entityID )
|
|
|
|
|| ucfirst($_);
|
|
|
|
$self->spList->{$entityID}->{confKey} = $_;
|
|
|
|
$self->spList->{$entityID}->{name} = $name;
|
|
|
|
|
|
|
|
# Set encryption mode
|
|
|
|
my $encryption_mode = $self->conf->{samlSPMetaDataOptions}->{$_}
|
2020-03-12 17:00:50 +01:00
|
|
|
->{samlSPMetaDataOptionsEncryptionMode} || "none";
|
2016-09-21 22:08:50 +02:00
|
|
|
my $lasso_encryption_mode = $self->getEncryptionMode($encryption_mode);
|
|
|
|
|
|
|
|
unless (
|
|
|
|
$self->setProviderEncryptionMode(
|
|
|
|
$self->lassoServer->get_provider($entityID),
|
|
|
|
$lasso_encryption_mode
|
|
|
|
)
|
|
|
|
)
|
|
|
|
{
|
2018-09-11 11:07:23 +02:00
|
|
|
$self->logger->error(
|
2016-09-21 22:08:50 +02:00
|
|
|
"Unable to set encryption mode $encryption_mode on SP $_");
|
2018-10-09 19:36:59 +02:00
|
|
|
next;
|
2016-09-21 22:08:50 +02:00
|
|
|
}
|
2018-10-09 19:36:59 +02:00
|
|
|
$self->logger->debug("Set encryption mode $encryption_mode on SP $_");
|
2016-09-21 22:08:50 +02:00
|
|
|
|
2019-09-28 00:18:16 +02:00
|
|
|
# Set signature method if overriden
|
|
|
|
my $signature_method = $self->conf->{samlSPMetaDataOptions}->{$_}
|
|
|
|
->{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 $_"
|
|
|
|
);
|
|
|
|
next;
|
|
|
|
}
|
|
|
|
$self->logger->debug(
|
|
|
|
"Set signature method $signature_method on SP $_");
|
|
|
|
}
|
|
|
|
|
2017-03-22 21:20:30 +01:00
|
|
|
my $rule = $self->conf->{samlSPMetaDataOptions}->{$_}
|
|
|
|
->{samlSPMetaDataOptionsRule};
|
|
|
|
if ( length $rule ) {
|
|
|
|
$rule = $self->p->HANDLER->substitute($rule);
|
|
|
|
unless ( $rule = $self->p->HANDLER->buildSub($rule) ) {
|
2018-09-11 11:07:23 +02:00
|
|
|
$self->logger->error( 'SAML SP rule error: '
|
2017-03-22 21:20:30 +01:00
|
|
|
. $self->p->HANDLER->tsv->{jail}->error );
|
2018-10-09 19:36:59 +02:00
|
|
|
next;
|
2017-03-22 21:20:30 +01:00
|
|
|
}
|
2020-01-24 09:01:56 +01:00
|
|
|
$self->spRules->{$_} = $rule;
|
2017-03-22 21:20:30 +01:00
|
|
|
}
|
|
|
|
|
2019-10-22 17:07:17 +02:00
|
|
|
# Load per-SP macros
|
|
|
|
my $macros = $self->conf->{samlSPMetaDataMacros}->{$_};
|
|
|
|
for my $macroAttr ( keys %{$macros} ) {
|
|
|
|
my $macroRule = $macros->{$macroAttr};
|
|
|
|
if ( length $macroRule ) {
|
|
|
|
$macroRule = $self->p->HANDLER->substitute($macroRule);
|
|
|
|
unless ( $macroRule = $self->p->HANDLER->buildSub($macroRule) )
|
|
|
|
{
|
|
|
|
$self->error( 'SAML SP macro error: '
|
|
|
|
. $self->p->HANDLER->tsv->{jail}->error );
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
$self->spMacros->{$entityID}->{$macroAttr} = $macroRule;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-02-15 07:41:50 +01:00
|
|
|
$self->logger->debug("SP $_ added");
|
2016-09-21 22:08:50 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
2016-09-22 22:12:56 +02:00
|
|
|
# Check SAML requests and responses
|
|
|
|
sub checkMessage {
|
|
|
|
my ( $self, $req, $url, $request_method, $content_type, $profile_type ) =
|
|
|
|
@_;
|
|
|
|
$profile_type ||= "login";
|
|
|
|
|
|
|
|
# Create Profile object
|
|
|
|
my $profile;
|
|
|
|
$profile = $self->createLogin( $self->lassoServer )
|
|
|
|
if ( $profile_type eq "login" );
|
|
|
|
$profile = $self->createLogout( $self->lassoServer )
|
|
|
|
if ( $profile_type eq "logout" );
|
|
|
|
|
|
|
|
# Get relayState
|
|
|
|
my $relaystate = $req->param('RelayState');
|
|
|
|
my ( $method, $request, $response, $artifact, $message );
|
|
|
|
|
|
|
|
# 2.1. HTTP REDIRECT
|
|
|
|
if ( $request_method eq 'GET' ) {
|
|
|
|
|
|
|
|
$method = Lasso::Constants::HTTP_METHOD_REDIRECT;
|
2017-02-15 07:41:50 +01:00
|
|
|
$self->logger->debug("SAML method: HTTP-REDIRECT");
|
2016-09-22 22:12:56 +02:00
|
|
|
|
|
|
|
if ( $req->param('SAMLResponse') ) {
|
|
|
|
|
|
|
|
# Response in query string
|
|
|
|
$response = $self->getQueryString($req);
|
2017-02-15 07:41:50 +01:00
|
|
|
$self->logger->debug("HTTP-REDIRECT: SAML Response $response");
|
2016-09-22 22:12:56 +02:00
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( $req->param('SAMLRequest') ) {
|
|
|
|
|
|
|
|
# Request in query string
|
|
|
|
$request = $self->getQueryString($req);
|
2017-02-15 07:41:50 +01:00
|
|
|
$self->logger->debug("HTTP-REDIRECT: SAML Request $request");
|
2016-09-22 22:12:56 +02:00
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( $req->param('SAMLart') ) {
|
|
|
|
|
|
|
|
# Artifact in query string
|
|
|
|
$artifact = $self->getQueryString($req);
|
2017-02-15 07:41:50 +01:00
|
|
|
$self->logger->debug("HTTP-REDIRECT: SAML Artifact $artifact");
|
2016-09-22 22:12:56 +02:00
|
|
|
|
|
|
|
# Resolve Artifact
|
2019-07-02 20:03:40 +02:00
|
|
|
$method = Lasso::Constants::HTTP_METHOD_ARTIFACT_GET;
|
2016-09-22 22:12:56 +02:00
|
|
|
$message = $self->resolveArtifact( $profile, $artifact, $method );
|
|
|
|
|
|
|
|
# Request or response ?
|
|
|
|
if ( $message =~ /samlp:response/i ) {
|
|
|
|
$response = $message;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
$request = $message;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
# 2.2. HTTP POST AND SOAP
|
|
|
|
elsif ( $request_method =~ /^POST$/ ) {
|
|
|
|
|
|
|
|
# 2.2.1. POST
|
|
|
|
if ( $content_type !~ /xml/ ) {
|
|
|
|
|
|
|
|
$method = Lasso::Constants::HTTP_METHOD_POST;
|
2017-02-15 07:41:50 +01:00
|
|
|
$self->logger->debug("SAML method: HTTP-POST");
|
2016-09-22 22:12:56 +02:00
|
|
|
|
|
|
|
if ( $req->param('SAMLResponse') ) {
|
|
|
|
|
|
|
|
# Response in body part
|
|
|
|
$response = $req->param('SAMLResponse');
|
2018-03-10 09:42:29 +01:00
|
|
|
$response =~ s/ /+/gs;
|
2017-02-15 07:41:50 +01:00
|
|
|
$self->logger->debug("HTTP-POST: SAML Response $response");
|
2016-09-22 22:12:56 +02:00
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
elsif ( $req->param('SAMLRequest') ) {
|
|
|
|
|
|
|
|
# Request in body part
|
|
|
|
$request = $req->param('SAMLRequest');
|
2017-02-15 07:41:50 +01:00
|
|
|
$self->logger->debug("HTTP-POST: SAML Request $request");
|
2016-09-22 22:12:56 +02:00
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
elsif ( $req->param('SAMLart') ) {
|
|
|
|
|
|
|
|
# Artifact in SAMLart param
|
|
|
|
$artifact = $req->param('SAMLart');
|
2017-02-15 07:41:50 +01:00
|
|
|
$self->logger->debug("HTTP-POST: SAML Artifact $artifact");
|
2016-09-22 22:12:56 +02:00
|
|
|
|
|
|
|
# Resolve Artifact
|
|
|
|
$method = Lasso::Constants::HTTP_METHOD_ARTIFACT_POST;
|
|
|
|
$message =
|
|
|
|
$self->resolveArtifact( $profile, $artifact, $method );
|
|
|
|
|
|
|
|
# Request or response ?
|
|
|
|
if ( $message =~ /samlp:response/i ) {
|
|
|
|
$response = $message;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
$request = $message;
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
# 2.2.2. SOAP
|
|
|
|
else {
|
|
|
|
|
|
|
|
$method = Lasso::Constants::HTTP_METHOD_SOAP;
|
2017-02-15 07:41:50 +01:00
|
|
|
$self->logger->debug("SAML method: HTTP-SOAP");
|
2016-09-22 22:12:56 +02:00
|
|
|
|
|
|
|
# SOAP is always a request
|
2017-01-07 08:45:36 +01:00
|
|
|
$request = $req->content;
|
2017-02-15 07:41:50 +01:00
|
|
|
$self->logger->debug("HTTP-SOAP: SAML Request $request");
|
2016-09-22 22:12:56 +02:00
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
# 4. Return values
|
|
|
|
return ( $request, $response, $method, $relaystate, $artifact ? 1 : 0 );
|
|
|
|
}
|
|
|
|
|
|
|
|
## @method boolean checkLassoError(Lasso::Error error, string level)
|
|
|
|
# Log Lasso error code and message if this is actually a Lasso::Error with code > 0
|
|
|
|
# @param error Lasso error object
|
|
|
|
# @param level optional log level (debug by default)
|
|
|
|
# @return 1 if no error
|
|
|
|
sub checkLassoError {
|
|
|
|
my ( $self, $error, $level ) = @_;
|
2020-02-03 17:06:49 +01:00
|
|
|
$level ||= 'error';
|
2016-09-22 22:12:56 +02:00
|
|
|
|
|
|
|
# If $error is not a Lasso::Error object, display error string
|
|
|
|
unless ( ref($error) and $error->isa("Lasso::Error") ) {
|
|
|
|
return 1 unless $error;
|
2017-02-15 07:41:50 +01:00
|
|
|
$self->p->lmLog( "Lasso error: $error", $level );
|
2016-09-22 22:12:56 +02:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
# Else check error code and error message
|
|
|
|
if ( $error->{code} ) {
|
2017-02-15 07:41:50 +01:00
|
|
|
$self->p->lmLog(
|
2016-09-22 22:12:56 +02:00
|
|
|
"Lasso error code " . $error->{code} . ": " . $error->{message},
|
|
|
|
$level );
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
## @method Lasso::Server createServer(string metadata, string private_key, string private_key_password, string private_key_enc, string private_key_enc_password, string certificate)
|
|
|
|
# Load service metadata and create Lasso::Server object
|
2018-07-05 22:56:16 +02:00
|
|
|
# @param metadata SAML metadata
|
2016-09-22 22:12:56 +02:00
|
|
|
# @param private_key private key
|
|
|
|
# @param private_key_password optional private key password
|
|
|
|
# @param private_key_enc optional private key for encryption
|
|
|
|
# @param private_key_enc_password optional private key password for encryption
|
|
|
|
# @param certificate optional certificate
|
|
|
|
# @return Lasso::Server object
|
|
|
|
sub createServer {
|
|
|
|
my ( $self, $metadata, $private_key, $private_key_password,
|
|
|
|
$private_key_enc, $private_key_enc_password, $certificate )
|
|
|
|
= @_;
|
|
|
|
my $server;
|
|
|
|
|
|
|
|
eval {
|
|
|
|
$server = Lasso::Server::new_from_buffers( $metadata, $private_key,
|
|
|
|
$private_key_password, $certificate );
|
|
|
|
|
|
|
|
# Set private key for encryption
|
|
|
|
if ($private_key_enc) {
|
|
|
|
Lasso::Server::set_encryption_private_key_with_password( $server,
|
|
|
|
$private_key_enc, $private_key_enc_password );
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
if ($@) {
|
|
|
|
$self->checkLassoError($@);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
return $server;
|
|
|
|
}
|
|
|
|
|
|
|
|
## @method boolean addIDP(Lasso::Server server, string metadata, string public_key, string ca_cert_chain)
|
|
|
|
# Add IDP to an existing Lasso::Server
|
|
|
|
# @param server Lasso::Server object
|
|
|
|
# @param metadata IDP metadata
|
|
|
|
# @param public_key optional public key
|
|
|
|
# @param ca_cert_chain optional ca cert chain
|
|
|
|
# @return boolean result
|
|
|
|
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 );
|
|
|
|
}
|
|
|
|
|
|
|
|
## @method boolean addSP(Lasso::Server server, string metadata, string public_key, string ca_cert_chain)
|
|
|
|
# Add SP to an existing Lasso::Server
|
|
|
|
# @param server Lasso::Server object
|
|
|
|
# @param metadata SP metadata
|
|
|
|
# @param public_key optional public key
|
|
|
|
# @param ca_cert_chain optional ca cert chain
|
|
|
|
# @return boolean result
|
|
|
|
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 );
|
|
|
|
}
|
|
|
|
|
|
|
|
## @method boolean addAA(Lasso::Server server, string metadata, string public_key, string ca_cert_chain)
|
|
|
|
# Add Attribute Authority to an existing Lasso::Server
|
|
|
|
# @param server Lasso::Server object
|
|
|
|
# @param metadata AA metadata
|
|
|
|
# @param public_key optional public key
|
|
|
|
# @param ca_cert_chain optional ca cert chain
|
|
|
|
# @return boolean result
|
|
|
|
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 );
|
|
|
|
}
|
|
|
|
|
|
|
|
## @method boolean addProvider(Lasso::Server server, int role, string metadata, string public_key, string ca_cert_chain)
|
|
|
|
# Add provider to an existing Lasso::Server
|
|
|
|
# @param server Lasso::Server object
|
|
|
|
# @param role (IDP, SP or Both)
|
|
|
|
# @param metadata IDP metadata
|
|
|
|
# @param public_key optional public key
|
|
|
|
# @param ca_cert_chain optional ca cert chain
|
|
|
|
# @return boolean result
|
|
|
|
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 );
|
|
|
|
|
|
|
|
eval {
|
|
|
|
Lasso::Server::add_provider_from_buffer( $server, $role, $metadata,
|
|
|
|
$public_key, $ca_cert_chain );
|
|
|
|
};
|
|
|
|
|
|
|
|
return $self->checkLassoError($@);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
## @method string getOrganizationName(Lasso::Server server, string idp)
|
|
|
|
# Return name of organization picked up from metadata
|
|
|
|
#@param server Lasso::Server object
|
|
|
|
#@param idp entity ID
|
|
|
|
#@return string organization name
|
|
|
|
sub getOrganizationName {
|
|
|
|
my ( $self, $server, $idp ) = @_;
|
|
|
|
my ( $provider, $node );
|
|
|
|
|
|
|
|
# Get provider from server
|
|
|
|
eval { $provider = Lasso::Server::get_provider( $server, $idp ); };
|
|
|
|
|
|
|
|
if ($@) {
|
|
|
|
$self->checkLassoError($@);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
# Get organization node
|
|
|
|
eval { $node = Lasso::Provider::get_organization($provider); };
|
|
|
|
|
|
|
|
if ($@) {
|
|
|
|
$self->checkLassoError($@);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
# Return if node is empty
|
|
|
|
return unless $node;
|
|
|
|
|
|
|
|
# Extract organization name
|
|
|
|
my $xs = XML::Simple->new();
|
|
|
|
my $data = $xs->XMLin($node);
|
|
|
|
return $data->{OrganizationName}->{content};
|
|
|
|
}
|
|
|
|
|
|
|
|
## @method string getNextProviderId(Lasso::Logout logout)
|
|
|
|
# Returns the provider id from providerID_index in list of providerIDs in
|
|
|
|
# principal session with the exception of initial service provider ID.
|
|
|
|
# @param logout Lasso::Logout object
|
|
|
|
# @return string
|
|
|
|
sub getNextProviderId {
|
|
|
|
my $self = shift;
|
|
|
|
my $logout = shift;
|
|
|
|
my $providerId;
|
|
|
|
|
|
|
|
eval { $providerId = Lasso::Logout::get_next_providerID($logout); };
|
|
|
|
if ($@) {
|
|
|
|
$self->checkLassoError($@);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
return $providerId;
|
|
|
|
}
|
|
|
|
|
|
|
|
## @method boolean resetProviderIdIndex(Lasso::Logout logout)
|
|
|
|
# Reset the providerID_index attribute in Lasso::Logout object
|
|
|
|
# @param logout Lasso::Logout object
|
|
|
|
# @return boolean
|
|
|
|
sub resetProviderIdIndex {
|
|
|
|
my $self = shift;
|
|
|
|
my $logout = shift;
|
|
|
|
|
|
|
|
eval { Lasso::Logout::reset_providerID_index($logout); };
|
|
|
|
return $self->checkLassoError($@);
|
|
|
|
}
|
|
|
|
|
|
|
|
## @method Lasso::Login createAuthnRequest(Lasso::Server server, string idp, int method, boolean forceAuthn, boolean isPassive, string nameIDFormat, boolean allowProxiedAuthn, boolean signSSOMessage, string requestedAuthnContext)
|
|
|
|
# Create authentication request for selected IDP
|
|
|
|
# @param server Lasso::Server object
|
|
|
|
# @param idp IDP entityID
|
|
|
|
# @param method HTTP method
|
|
|
|
# @param forceAuthn force authentication on IDP
|
|
|
|
# @param isPassive require passive authentication
|
|
|
|
# @param nameIDFormat SAML2 NameIDFormat
|
|
|
|
# @param allowProxiedAuthn allow proxy on IDP
|
|
|
|
# @param signSSOMessage sign request
|
|
|
|
# @param requestedAuthnContext authentication context
|
|
|
|
# @return Lasso::Login object
|
|
|
|
sub createAuthnRequest {
|
|
|
|
my (
|
|
|
|
$self, $req, $server,
|
|
|
|
$idp, $method, $forceAuthn,
|
|
|
|
$isPassive, $nameIDFormat, $allowProxiedAuthn,
|
|
|
|
$signSSOMessage, $requestedAuthnContext
|
|
|
|
) = @_;
|
|
|
|
my $proxyCount;
|
|
|
|
my $proxyRequestedAuthnContext;
|
|
|
|
|
|
|
|
# Create Lasso Login
|
|
|
|
my $login = $self->createLogin($server);
|
|
|
|
|
|
|
|
unless ($login) {
|
2017-02-15 07:41:50 +01:00
|
|
|
$self->logger->error('Unable to create Lasso login');
|
2016-09-22 22:12:56 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
# Init authentication request
|
|
|
|
unless ( $self->initAuthnRequest( $login, $idp, $method ) ) {
|
2017-02-15 07:41:50 +01:00
|
|
|
$self->logger->error(
|
|
|
|
"Could not initiate authentication request on $idp");
|
2016-09-22 22:12:56 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
# Set RelayState
|
2017-02-19 12:56:20 +01:00
|
|
|
if ( my $relaystate = $self->storeRelayState( $req, 'urldc' ) ) {
|
2016-09-22 22:12:56 +02:00
|
|
|
$login->msg_relayState($relaystate);
|
2017-02-15 07:41:50 +01:00
|
|
|
$self->logger->debug("Set $relaystate in RelayState");
|
2016-09-22 22:12:56 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
# Customize request
|
|
|
|
my $request = $login->request();
|
|
|
|
|
|
|
|
# Maybe we are in IDP proxy mode (SAML request received on IDP side)
|
|
|
|
# In this case:
|
|
|
|
# * Check proxy conditions
|
|
|
|
# * Forward some authn constraints
|
2018-07-05 22:56:16 +02:00
|
|
|
if ( $req->data->{_proxiedSamlRequest} ) {
|
2016-09-22 22:12:56 +02:00
|
|
|
|
2017-02-15 07:41:50 +01:00
|
|
|
$self->logger->debug("IDP Proxy mode detected");
|
2016-09-22 22:12:56 +02:00
|
|
|
|
|
|
|
# Get ProxyCount value
|
|
|
|
eval {
|
|
|
|
$proxyCount =
|
2018-07-05 22:56:16 +02:00
|
|
|
$req->data->{_proxiedSamlRequest}->Scoping()->ProxyCount();
|
2016-09-22 22:12:56 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
# Deny request if ProxyCount eq 0
|
|
|
|
if ( defined $proxyCount ) {
|
|
|
|
|
2017-02-15 07:41:50 +01:00
|
|
|
$self->logger->debug(
|
|
|
|
"Found proxyCount $proxyCount in proxied request");
|
2016-09-22 22:12:56 +02:00
|
|
|
|
|
|
|
if ( $proxyCount eq 0 ) {
|
2017-02-15 07:41:50 +01:00
|
|
|
$self->userLogger->error(
|
|
|
|
"SAML request cannot be proxied (ProxyCount 0)");
|
2016-09-22 22:12:56 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
|
|
|
|
# Decrease ProxyCount
|
2018-07-05 22:56:16 +02:00
|
|
|
my $scoping = $req->data->{_proxiedSamlRequest}->Scoping();
|
2016-09-22 22:12:56 +02:00
|
|
|
$scoping->ProxyCount( $proxyCount-- );
|
|
|
|
eval { $request->Scoping($scoping); };
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
# isPassive
|
2018-07-05 22:56:16 +02:00
|
|
|
eval { $isPassive = $req->data->{_proxiedSamlRequest}->IsPassive(); };
|
2016-09-22 22:12:56 +02:00
|
|
|
|
|
|
|
# forceAuthn
|
2018-07-05 22:56:16 +02:00
|
|
|
eval { $forceAuthn = $req->data->{_proxiedSamlRequest}->ForceAuthn(); };
|
2016-09-22 22:12:56 +02:00
|
|
|
|
|
|
|
# requestedAuthnContext
|
|
|
|
eval {
|
|
|
|
$proxyRequestedAuthnContext =
|
2018-07-05 22:56:16 +02:00
|
|
|
$req->data->{_proxiedSamlRequest}->RequestedAuthnContext();
|
2016-09-22 22:12:56 +02:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
# NameIDFormat
|
|
|
|
if ($nameIDFormat) {
|
2017-02-15 07:41:50 +01:00
|
|
|
$self->logger->debug("Use NameIDFormat $nameIDFormat");
|
2016-09-22 22:12:56 +02:00
|
|
|
$request->NameIDPolicy()->Format($nameIDFormat);
|
|
|
|
}
|
|
|
|
|
|
|
|
# Always allow NameID creation
|
|
|
|
$request->NameIDPolicy()->AllowCreate(1);
|
|
|
|
|
|
|
|
# Force authentication
|
|
|
|
if ($forceAuthn) {
|
2017-02-15 07:41:50 +01:00
|
|
|
$self->logger->debug("Force authentication on IDP");
|
2016-09-22 22:12:56 +02:00
|
|
|
$request->ForceAuthn(1);
|
|
|
|
}
|
|
|
|
|
|
|
|
# Passive authentication
|
|
|
|
if ($isPassive) {
|
2017-02-15 07:41:50 +01:00
|
|
|
$self->logger->debug("Passive authentication on IDP");
|
2016-09-22 22:12:56 +02:00
|
|
|
$request->IsPassive(1);
|
|
|
|
}
|
|
|
|
|
|
|
|
# Allow proxy
|
|
|
|
unless ($allowProxiedAuthn) {
|
2017-02-15 07:41:50 +01:00
|
|
|
$self->logger->debug("Do not allow this request to be proxied");
|
2016-09-22 22:12:56 +02:00
|
|
|
eval {
|
|
|
|
my $proxyRestriction = Lasso::Saml2ProxyRestriction->new();
|
|
|
|
$proxyRestriction->Audience($idp);
|
|
|
|
$proxyRestriction->Count(0);
|
|
|
|
my $conditions = $request->Conditions()
|
|
|
|
|| Lasso::Saml2Conditions->new();
|
|
|
|
$conditions->ProxyRestriction($proxyRestriction);
|
|
|
|
$request->Conditions($conditions);
|
|
|
|
};
|
|
|
|
if ($@) {
|
|
|
|
$self->checkLassoError($@);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
# Signature
|
|
|
|
if ( $signSSOMessage == 0 ) {
|
2017-02-15 07:41:50 +01:00
|
|
|
$self->logger->debug("SSO request will not be signed");
|
2016-09-22 22:12:56 +02:00
|
|
|
$self->disableSignature($login);
|
|
|
|
}
|
|
|
|
elsif ( $signSSOMessage == 1 ) {
|
2017-02-15 07:41:50 +01:00
|
|
|
$self->logger->debug("SSO request will be signed");
|
2016-09-22 22:12:56 +02:00
|
|
|
$self->forceSignature($login);
|
|
|
|
}
|
|
|
|
else {
|
2017-02-15 07:41:50 +01:00
|
|
|
$self->logger->debug("SSO request signature according to metadata");
|
2016-09-22 22:12:56 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
# Requested authentication context
|
|
|
|
if ($proxyRequestedAuthnContext) {
|
2017-02-15 07:41:50 +01:00
|
|
|
$self->logger->debug("Use RequestedAuthnContext from proxied request");
|
2016-09-22 22:12:56 +02:00
|
|
|
$request->RequestedAuthnContext($proxyRequestedAuthnContext);
|
|
|
|
}
|
|
|
|
elsif ($requestedAuthnContext) {
|
2017-02-15 07:41:50 +01:00
|
|
|
$self->logger->debug("Request $requestedAuthnContext context");
|
2016-09-22 22:12:56 +02:00
|
|
|
eval {
|
|
|
|
my $context = Lasso::Samlp2RequestedAuthnContext->new();
|
|
|
|
$context->AuthnContextClassRef($requestedAuthnContext);
|
|
|
|
$context->Comparison("minimum");
|
|
|
|
$request->RequestedAuthnContext($context);
|
|
|
|
};
|
|
|
|
if ($@) {
|
|
|
|
$self->checkLassoError($@);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
# Build authentication request
|
|
|
|
unless ( $self->buildAuthnRequestMsg($login) ) {
|
2017-02-15 07:41:50 +01:00
|
|
|
$self->logger->error("Could not build authentication request on $idp");
|
2016-09-22 22:12:56 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
# Artifact
|
|
|
|
if ( $method == $self->getHttpMethod("artifact-get")
|
|
|
|
or $method == $self->getHttpMethod("artifact-post") )
|
|
|
|
{
|
|
|
|
|
|
|
|
# Get artifact ID and Content, and store them
|
|
|
|
my $artifact_id = $login->get_artifact;
|
|
|
|
my $artifact_message = $login->get_artifact_message;
|
|
|
|
|
|
|
|
$self->storeArtifact( $artifact_id, $artifact_message );
|
|
|
|
}
|
|
|
|
return $login;
|
|
|
|
}
|
|
|
|
|
|
|
|
## @method Lasso::Login createLogin(Lasso::Server server, string dump)
|
|
|
|
# Create Lasso::Login object
|
|
|
|
# @param server Lasso::Server object
|
|
|
|
# @param dump optional XML dump
|
|
|
|
# @return Lasso::Login object
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
|
|
|
## @method boolean initAuthnRequest(Lasso::Login login, string idp, int method)
|
|
|
|
# Init authentication request
|
|
|
|
# @param login Lasso::Login
|
|
|
|
# @param idp entityID
|
|
|
|
# @param method HTTP method
|
|
|
|
# @return boolean result
|
|
|
|
sub initAuthnRequest {
|
|
|
|
my ( $self, $login, $idp, $method ) = @_;
|
|
|
|
|
|
|
|
eval { Lasso::Login::init_authn_request( $login, $idp, $method ); };
|
|
|
|
|
|
|
|
return $self->checkLassoError($@);
|
|
|
|
}
|
|
|
|
|
|
|
|
## @method boolean initIdpInitiatedAuthnRequest(Lasso::Login login, string idp)
|
|
|
|
# Init authentication request
|
|
|
|
# @param login Lasso::Login
|
|
|
|
# @param idp entityID
|
|
|
|
# @return boolean result
|
|
|
|
sub initIdpInitiatedAuthnRequest {
|
|
|
|
my ( $self, $login, $idp ) = @_;
|
|
|
|
|
|
|
|
eval { Lasso::Login::init_idp_initiated_authn_request( $login, $idp ); };
|
|
|
|
|
|
|
|
return $self->checkLassoError($@);
|
|
|
|
}
|
|
|
|
|
|
|
|
## @method boolean buildAuthnRequestMsg(Lasso::Login login)
|
|
|
|
# Build authentication request message
|
|
|
|
# @param login Lasso::Login
|
|
|
|
# @return boolean result
|
|
|
|
sub buildAuthnRequestMsg {
|
|
|
|
my ( $self, $login ) = @_;
|
|
|
|
|
|
|
|
eval { Lasso::Login::build_authn_request_msg($login); };
|
|
|
|
|
|
|
|
return $self->checkLassoError($@);
|
|
|
|
}
|
|
|
|
|
|
|
|
## @method boolean processAuthnRequestMsg(Lasso::Login login, string request)
|
|
|
|
# Process authentication request message
|
|
|
|
# @param login Lasso::Login object
|
|
|
|
# @param request SAML request
|
|
|
|
# @return result
|
|
|
|
sub processAuthnRequestMsg {
|
2020-02-03 17:06:00 +01:00
|
|
|
my ( $self, $login, $request, $level ) = @_;
|
2016-09-22 22:12:56 +02:00
|
|
|
|
|
|
|
eval { Lasso::Login::process_authn_request_msg( $login, $request ); };
|
|
|
|
|
2020-02-03 17:06:00 +01:00
|
|
|
return $self->checkLassoError( $@, $level );
|
2016-09-22 22:12:56 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
## @method boolean validateRequestMsg(Lasso::Login login, boolean auth, boolean consent)
|
|
|
|
# Validate request message
|
|
|
|
# @param login Lasso::Login object
|
|
|
|
# @param auth is user authenticated?
|
|
|
|
# @param consent is consent obtained?
|
|
|
|
# @return result
|
|
|
|
sub validateRequestMsg {
|
|
|
|
my ( $self, $login, $auth, $consent ) = @_;
|
|
|
|
|
|
|
|
eval { Lasso::Login::validate_request_msg( $login, $auth, $consent ); };
|
|
|
|
|
|
|
|
return $self->checkLassoError($@);
|
|
|
|
}
|
|
|
|
|
|
|
|
## @method boolean buildAuthnResponseMsg(Lasso::Login login)
|
|
|
|
# Build authentication response message
|
|
|
|
# @param login Lasso::Login object
|
|
|
|
# @return boolean result
|
|
|
|
sub buildAuthnResponseMsg {
|
|
|
|
my ( $self, $login ) = @_;
|
|
|
|
|
|
|
|
eval { Lasso::Login::build_authn_response_msg($login); };
|
|
|
|
|
|
|
|
return $self->checkLassoError($@);
|
|
|
|
}
|
|
|
|
|
|
|
|
## @method boolean buildArtifactMsg(Lasso::Login login, int method)
|
|
|
|
# Build artifact message
|
|
|
|
# @param login Lasso::Login object
|
|
|
|
# @param method HTTP method
|
|
|
|
# @return boolean result
|
|
|
|
sub buildArtifactMsg {
|
|
|
|
my ( $self, $login, $method ) = @_;
|
|
|
|
|
|
|
|
eval { Lasso::Login::build_artifact_msg( $login, $method ); };
|
|
|
|
|
|
|
|
return $self->checkLassoError($@);
|
|
|
|
}
|
|
|
|
|
|
|
|
## @method boolean buildAssertion(Lasso::Login login, string authn_context, int notOnOrAfterTimeout)
|
|
|
|
# Build assertion
|
|
|
|
# @param login Lasso::Login object
|
|
|
|
# @param authn_context SAML2 authentication context
|
|
|
|
# @param notOnOrAfterTimeout Timeout to apply to notOnOrAfter
|
|
|
|
# @return boolean result
|
|
|
|
sub buildAssertion {
|
|
|
|
my ( $self, $req, $login, $authn_context, $notOnOrAfterTimeout ) = @_;
|
|
|
|
$notOnOrAfterTimeout ||= $self->conf->{timeout};
|
|
|
|
|
|
|
|
# Dates
|
|
|
|
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($@);
|
|
|
|
}
|
|
|
|
|
|
|
|
## @method boolean processAuthnResponseMsg(Lasso::Login login, string response)
|
|
|
|
# Process authentication response message
|
|
|
|
# @param login Lasso::Login object
|
|
|
|
# @param response SAML response
|
|
|
|
# @return result
|
|
|
|
sub processAuthnResponseMsg {
|
|
|
|
my ( $self, $login, $response ) = @_;
|
|
|
|
|
|
|
|
eval { Lasso::Login::process_authn_response_msg( $login, $response ); };
|
|
|
|
|
|
|
|
return $self->checkLassoError($@);
|
|
|
|
}
|
|
|
|
|
2016-12-28 17:50:39 +01:00
|
|
|
# UNUSED
|
|
|
|
### @method Lasso::Saml2NameID getNameIdentifer(Lasso::Profile profile)
|
|
|
|
## Get NameID from Lasso Profile
|
|
|
|
## @param profile Lasso::Profile object
|
|
|
|
## @return result or NULL if error
|
|
|
|
#sub getNameIdentifier {
|
|
|
|
# my ( $self, $profile ) = @_;
|
|
|
|
# my $nameid;
|
|
|
|
#
|
|
|
|
# eval { $nameid = Lasso::Profile::get_nameIdentifier($profile); };
|
|
|
|
#
|
|
|
|
# if ($@) {
|
|
|
|
# $self->checkLassoError($@);
|
|
|
|
# return;
|
|
|
|
# }
|
|
|
|
#
|
|
|
|
# return $nameid;
|
|
|
|
#}
|
|
|
|
|
|
|
|
# UNUSED
|
|
|
|
### @method Lasso::Identity createIdentity(string dump)
|
|
|
|
## Create Lasso::Identity object
|
|
|
|
## @param dump optional Identity dump
|
|
|
|
## @return Lasso::Identity object
|
|
|
|
#sub createIdentity {
|
|
|
|
# my ( $self, $dump ) = @_;
|
|
|
|
# my $identity;
|
|
|
|
#
|
|
|
|
# if ($dump) {
|
|
|
|
# eval { $identity = Lasso::Identity::new_from_dump($dump); };
|
|
|
|
# }
|
|
|
|
# else {
|
|
|
|
# eval { $identity = Lasso::Identity->new(); };
|
|
|
|
# }
|
|
|
|
#
|
|
|
|
# if ($@) {
|
|
|
|
# $self->checkLassoError($@);
|
|
|
|
# return;
|
|
|
|
# }
|
|
|
|
#
|
|
|
|
# return $identity;
|
|
|
|
#}
|
|
|
|
|
|
|
|
## UNUSED
|
|
|
|
### @method Lasso::Session createSession(string dump)
|
|
|
|
## Create Lasso::Session object
|
|
|
|
## @param dump optional Session dump
|
|
|
|
## @return Lasso::Session object
|
|
|
|
#sub createSession {
|
|
|
|
# my ( $self, $dump ) = @_;
|
|
|
|
# my $session;
|
|
|
|
#
|
|
|
|
# if ($dump) {
|
|
|
|
# eval { $session = Lasso::Session::new_from_dump($dump); };
|
|
|
|
# }
|
|
|
|
# else {
|
|
|
|
# eval { $session = Lasso::Session->new(); };
|
|
|
|
# }
|
|
|
|
#
|
|
|
|
# if ($@) {
|
|
|
|
# $self->checkLassoError($@);
|
|
|
|
# return;
|
|
|
|
# }
|
|
|
|
#
|
|
|
|
# return $session;
|
|
|
|
#}
|
2016-09-22 22:12:56 +02:00
|
|
|
|
|
|
|
## @method boolean acceptSSO(Lasso::Login login)
|
|
|
|
# Accept SSO from IDP
|
|
|
|
# @param login Lasso::Login object
|
|
|
|
# @return result
|
|
|
|
sub acceptSSO {
|
|
|
|
my ( $self, $login ) = @_;
|
|
|
|
|
|
|
|
eval { Lasso::Login::accept_sso($login); };
|
|
|
|
|
|
|
|
return $self->checkLassoError($@);
|
|
|
|
}
|
|
|
|
|
|
|
|
## @method string storeRelayState(hashref infos)
|
|
|
|
# Store information in relayState database and return
|
|
|
|
# corresponding session_id
|
|
|
|
# @param infos HASH reference of information
|
|
|
|
sub storeRelayState {
|
|
|
|
my ( $self, $req, @data ) = @_;
|
|
|
|
|
|
|
|
# check if there are data to store
|
|
|
|
my $infos;
|
|
|
|
foreach (@data) {
|
|
|
|
$infos->{$_} = $req->{$_} if $req->{$_};
|
|
|
|
}
|
|
|
|
return unless ($infos);
|
|
|
|
|
|
|
|
# Session type
|
|
|
|
$infos->{_type} = "relaystate";
|
|
|
|
|
|
|
|
# Set _utime for session autoremove
|
|
|
|
# Use default session timeout and relayState session timeout to compute it
|
|
|
|
my $time = time();
|
|
|
|
my $timeout = $self->conf->{timeout};
|
|
|
|
my $samlRelayStateTimeout =
|
|
|
|
$self->conf->{samlRelayStateTimeout} || $timeout;
|
|
|
|
|
|
|
|
$infos->{_utime} = $time + ( $samlRelayStateTimeout - $timeout );
|
|
|
|
|
2017-02-20 22:00:05 +01:00
|
|
|
# Create relaystate session and store infos in relaystate session
|
|
|
|
my $samlSessionInfo = $self->getSamlSession( undef, $infos )
|
|
|
|
or return undef;
|
2016-09-22 22:12:56 +02:00
|
|
|
|
|
|
|
# Session ID
|
|
|
|
my $relaystate_id = $samlSessionInfo->id;
|
|
|
|
|
|
|
|
# Return session ID
|
|
|
|
return $relaystate_id;
|
|
|
|
}
|
|
|
|
|
2016-10-04 11:18:27 +02:00
|
|
|
## @method boolean extractRelayState(string relaystate, boolean relayStateURL)
|
2016-09-22 22:12:56 +02:00
|
|
|
# Extract RelayState information into $self
|
|
|
|
# @param relaystate Relay state value
|
2016-10-04 11:18:27 +02:00
|
|
|
# @param relayStateURL Allow Relay state value to be the redirection URL
|
2016-09-22 22:12:56 +02:00
|
|
|
# @return result
|
|
|
|
sub extractRelayState {
|
2016-10-04 11:18:27 +02:00
|
|
|
my ( $self, $req, $relaystate, $relayStateURL ) = @_;
|
2016-09-22 22:12:56 +02:00
|
|
|
|
|
|
|
return 0 unless $relaystate;
|
2016-10-04 11:18:27 +02:00
|
|
|
if ( $relayStateURL and $relaystate =~ /^https?:\/\// ) {
|
2017-02-15 07:41:50 +01:00
|
|
|
$self->logger->debug("RelayState is a redirection URL: $relaystate");
|
2016-10-04 11:18:27 +02:00
|
|
|
$req->{urldc} = $relaystate;
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
else {
|
2016-09-22 22:12:56 +02:00
|
|
|
|
2016-10-04 11:18:27 +02:00
|
|
|
# Open relaystate session
|
|
|
|
my $samlSessionInfo = $self->getSamlSession($relaystate);
|
2016-09-22 22:12:56 +02:00
|
|
|
|
2016-10-04 11:18:27 +02:00
|
|
|
return 0 unless $samlSessionInfo;
|
2016-09-22 22:12:56 +02:00
|
|
|
|
2016-10-04 11:18:27 +02:00
|
|
|
# Push values in $self
|
|
|
|
foreach ( keys %{ $samlSessionInfo->data } ) {
|
|
|
|
next if $_ =~ /(type|_session_id|_utime)/;
|
2017-03-22 21:20:30 +01:00
|
|
|
if ( $_ eq 'issuerUrldc' ) {
|
|
|
|
$req->urldc( $samlSessionInfo->data->{$_} );
|
2017-03-21 07:23:58 +01:00
|
|
|
}
|
|
|
|
else {
|
|
|
|
$req->{$_} = $samlSessionInfo->data->{$_};
|
|
|
|
}
|
2016-10-04 11:18:27 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
# delete relaystate session
|
|
|
|
if ( $samlSessionInfo->remove ) {
|
2017-02-15 07:41:50 +01:00
|
|
|
$self->logger->debug("Relaystate $relaystate was deleted");
|
2016-10-04 11:18:27 +02:00
|
|
|
}
|
|
|
|
else {
|
2017-02-15 07:41:50 +01:00
|
|
|
$self->logger->error("Unable to delete relaystate $relaystate");
|
|
|
|
$self->logger->error( $samlSessionInfo->error );
|
2016-10-04 11:18:27 +02:00
|
|
|
}
|
2016-09-22 22:12:56 +02:00
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
## @method Lasso::Node getAssertion(Lasso::Login login)
|
|
|
|
# Get assertion in Lasso::Login object
|
|
|
|
# @param login Lasso::Login object
|
|
|
|
# @return assertion Lasso::Node object
|
|
|
|
sub getAssertion {
|
|
|
|
my ( $self, $login ) = @_;
|
|
|
|
my $assertion;
|
|
|
|
|
|
|
|
eval { $assertion = Lasso::Login::get_assertion($login); };
|
|
|
|
|
|
|
|
if ($@) {
|
|
|
|
$self->checkLassoError($@);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
return $assertion;
|
|
|
|
}
|
|
|
|
|
|
|
|
## @method string getAttributeValue(string name, string format, string friendly_name, arrayref attributes, boolean force_utf8)
|
|
|
|
# Get SAML attribute value corresponding to name, format and friendly_name
|
|
|
|
# Multivaluated values are separated by multiValuesSeparator
|
|
|
|
# If force_utf8 flag is set, value is encoded in UTF-8
|
|
|
|
# @param name SAML attribute name
|
|
|
|
# @param format optional SAML attribute format
|
|
|
|
# @param friendly_name optional SAML attribute friendly name
|
|
|
|
# @param attributes Attributes
|
|
|
|
# @param force_utf8 optional flag to force value in UTF-8
|
|
|
|
# @return attribute value
|
|
|
|
sub getAttributeValue {
|
|
|
|
my ( $self, $name, $format, $friendly_name, $attributes, $force_utf8 ) = @_;
|
|
|
|
my $value;
|
|
|
|
|
|
|
|
# Loop on attributes
|
|
|
|
foreach (@$attributes) {
|
|
|
|
my $attr_name = $_->Name();
|
|
|
|
my $attr_format = $_->NameFormat();
|
|
|
|
my $attr_fname = $_->FriendlyName();
|
|
|
|
|
|
|
|
# Skip if name does not correspond to attribute name
|
|
|
|
next if ( $name ne $attr_name );
|
|
|
|
|
|
|
|
# Verify format and friendly name if given
|
|
|
|
next if ( $format and $format ne $attr_format );
|
|
|
|
next if ( $friendly_name and $friendly_name ne $attr_fname );
|
|
|
|
|
|
|
|
# Attribute is found, return its content
|
|
|
|
my @attr_values = $_->AttributeValue();
|
|
|
|
|
|
|
|
foreach (@attr_values) {
|
|
|
|
my $xs = XML::Simple->new();
|
|
|
|
my $data = $xs->XMLin( $_->dump() );
|
|
|
|
my $content = $data->{content};
|
|
|
|
$value .= $content . $self->conf->{multiValuesSeparator}
|
|
|
|
if $content;
|
|
|
|
}
|
2019-05-23 15:52:11 +02:00
|
|
|
$value =~ s/$self->{conf}->{multiValuesSeparator}$// if $value;
|
2016-09-22 22:12:56 +02:00
|
|
|
|
|
|
|
# Encode UTF-8 if force_utf8 flag
|
|
|
|
$value = encode( "utf8", $value ) if $force_utf8;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
return $value;
|
|
|
|
}
|
|
|
|
|
2016-09-29 21:25:02 +02:00
|
|
|
## @method boolean validateConditions(Lasso::Saml2::Assertion assertion, string entityID, boolean checkTime, boolean checkAudience)
|
2016-09-22 22:12:56 +02:00
|
|
|
# Validate conditions
|
|
|
|
# @param assertion SAML2 assertion
|
|
|
|
# @param entityID relying party entity ID
|
2016-09-29 21:25:02 +02:00
|
|
|
# @param checkTime Enable time conditions check
|
|
|
|
# @param checkAudience Enable audience conditions check
|
2016-09-22 22:12:56 +02:00
|
|
|
# @return result
|
|
|
|
sub validateConditions {
|
2016-09-29 21:25:02 +02:00
|
|
|
my ( $self, $assertion, $entityID, $checkTime, $checkAudience ) = @_;
|
2016-09-22 22:12:56 +02:00
|
|
|
my $tolerance = 10;
|
|
|
|
my $status;
|
2016-09-29 21:25:02 +02:00
|
|
|
$checkTime = 1 unless defined $checkTime;
|
|
|
|
$checkAudience = 1 unless defined $checkAudience;
|
2016-09-22 22:12:56 +02:00
|
|
|
|
|
|
|
# Time
|
2016-09-29 21:25:02 +02:00
|
|
|
if ($checkTime) {
|
|
|
|
eval {
|
2017-02-15 07:41:50 +01:00
|
|
|
$status = Lasso::Saml2Assertion::validate_time_checks( $assertion,
|
2016-09-29 21:25:02 +02:00
|
|
|
$tolerance );
|
|
|
|
};
|
2016-09-22 22:12:56 +02:00
|
|
|
|
2016-09-29 21:25:02 +02:00
|
|
|
if ($@) {
|
|
|
|
$self->checkLassoError($@);
|
|
|
|
return 0;
|
|
|
|
}
|
2016-09-22 22:12:56 +02:00
|
|
|
|
2016-09-29 21:25:02 +02:00
|
|
|
unless ( $status eq Lasso::Constants::SAML2_ASSERTION_VALID ) {
|
2017-02-15 07:41:50 +01:00
|
|
|
$self->logger->error("Time conditions validations result: $status");
|
2016-09-29 21:25:02 +02:00
|
|
|
return 0;
|
|
|
|
}
|
2016-09-22 22:12:56 +02:00
|
|
|
|
2017-02-15 07:41:50 +01:00
|
|
|
$self->logger->debug("Time conditions validated");
|
2016-09-29 21:25:02 +02:00
|
|
|
}
|
|
|
|
else {
|
2017-02-15 07:41:50 +01:00
|
|
|
$self->logger->debug("Time conditions not checked");
|
2016-09-29 21:25:02 +02:00
|
|
|
}
|
2016-09-22 22:12:56 +02:00
|
|
|
|
|
|
|
# Audience
|
2016-09-29 21:25:02 +02:00
|
|
|
if ($checkAudience) {
|
|
|
|
eval {
|
|
|
|
$status =
|
|
|
|
Lasso::Saml2Assertion::validate_audience( $assertion, $entityID );
|
|
|
|
};
|
2016-09-22 22:12:56 +02:00
|
|
|
|
2016-09-29 21:25:02 +02:00
|
|
|
if ($@) {
|
|
|
|
$self->checkLassoError($@);
|
|
|
|
return 0;
|
|
|
|
}
|
2016-09-22 22:12:56 +02:00
|
|
|
|
2016-09-29 21:25:02 +02:00
|
|
|
unless ( $status eq Lasso::Constants::SAML2_ASSERTION_VALID ) {
|
2017-02-15 07:41:50 +01:00
|
|
|
$self->logger->error(
|
|
|
|
"Audience conditions validations result: $status");
|
2016-09-29 21:25:02 +02:00
|
|
|
return 0;
|
|
|
|
}
|
2016-09-22 22:12:56 +02:00
|
|
|
|
2017-02-15 07:41:50 +01:00
|
|
|
$self->logger->debug("Audience conditions validated");
|
2016-09-29 21:25:02 +02:00
|
|
|
}
|
|
|
|
else {
|
2017-02-15 07:41:50 +01:00
|
|
|
$self->logger->debug("Audience conditions not checked");
|
2016-09-29 21:25:02 +02:00
|
|
|
}
|
2016-09-22 22:12:56 +02:00
|
|
|
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
## @method Lasso::Logout createLogoutRequest(Lasso::Server server, string session_dump, int method, boolean signSLOMessage)
|
|
|
|
# Create logout request for selected entity
|
|
|
|
# @param server Lasso::Server object
|
|
|
|
# @param session_dump Lasso::Session dump
|
|
|
|
# @param method HTTP method
|
|
|
|
# @param signSLOMessage sign request
|
|
|
|
# @return Lasso::Login object
|
|
|
|
sub createLogoutRequest {
|
|
|
|
my ( $self, $req, $server, $session_dump, $method, $signSLOMessage ) = @_;
|
|
|
|
my $session;
|
|
|
|
|
|
|
|
# Create Lasso Logout
|
|
|
|
my $logout = $self->createLogout($server);
|
|
|
|
|
|
|
|
unless ( $self->setSessionFromDump( $logout, $session_dump ) ) {
|
2017-02-15 07:41:50 +01:00
|
|
|
$self->logger->error("Could not fill Lasso::Logout with session dump");
|
2016-09-22 22:12:56 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
# Init logout request
|
|
|
|
unless ( $self->initLogoutRequest( $logout, undef, $method ) ) {
|
2017-02-15 07:41:50 +01:00
|
|
|
$self->logger->error("Could not initiate logout request");
|
2016-09-22 22:12:56 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
# Set RelayState
|
2020-08-04 18:28:18 +02:00
|
|
|
if ( my $relaystate =
|
|
|
|
$self->storeRelayState( $req, 'urldc', 'issuerUrldc' ) )
|
|
|
|
{
|
2016-09-22 22:12:56 +02:00
|
|
|
$logout->msg_relayState($relaystate);
|
2017-02-15 07:41:50 +01:00
|
|
|
$self->logger->debug("Set $relaystate in RelayState");
|
2016-09-22 22:12:56 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
# Signature
|
|
|
|
if ( $signSLOMessage == 0 ) {
|
2017-02-15 07:41:50 +01:00
|
|
|
$self->logger->debug("SLO request will not be signed");
|
2016-09-22 22:12:56 +02:00
|
|
|
$self->disableSignature($logout);
|
|
|
|
}
|
|
|
|
elsif ( $signSLOMessage == 1 ) {
|
2017-02-15 07:41:50 +01:00
|
|
|
$self->logger->debug("SLO request will be signed");
|
2016-09-22 22:12:56 +02:00
|
|
|
$self->forceSignature($logout);
|
|
|
|
}
|
|
|
|
else {
|
2017-02-15 07:41:50 +01:00
|
|
|
$self->logger->debug("SLO request signature according to metadata");
|
2016-09-22 22:12:56 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
# Build logout request
|
|
|
|
unless ( $self->buildLogoutRequestMsg($logout) ) {
|
2017-02-15 07:41:50 +01:00
|
|
|
$self->logger->error("Could not build logout request");
|
2016-09-22 22:12:56 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
return $logout;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
## @method Lasso::Logout createLogout(Lasso::Server server, string dump)
|
|
|
|
# Create Lasso::Logout object
|
|
|
|
# @param server Lasso::Server object
|
|
|
|
# @param dump optional XML dump
|
|
|
|
# @return Lasso::Logout object
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
|
|
|
## @method boolean initLogoutRequest(Lasso::Logout logout, string entityID, int method)
|
|
|
|
# Init logout request
|
|
|
|
# @param logout Lasso::Logout object
|
|
|
|
# @param entityID Entity ID
|
|
|
|
# @param method HTTP method
|
|
|
|
# @return result
|
|
|
|
sub initLogoutRequest {
|
|
|
|
my ( $self, $logout, $entityID, $method ) = @_;
|
|
|
|
|
|
|
|
eval { Lasso::Logout::init_request( $logout, $entityID, $method ); };
|
|
|
|
|
|
|
|
return $self->checkLassoError($@);
|
|
|
|
}
|
|
|
|
|
|
|
|
## @method boolean buildLogoutRequestMsg(Lasso::Logout logout)
|
|
|
|
# Build logout request message
|
|
|
|
# @param logout Lasso::Logout object
|
|
|
|
# @return result
|
|
|
|
sub buildLogoutRequestMsg {
|
|
|
|
my ( $self, $logout ) = @_;
|
|
|
|
|
|
|
|
eval { Lasso::Logout::build_request_msg($logout); };
|
|
|
|
|
|
|
|
return $self->checkLassoError($@);
|
|
|
|
}
|
|
|
|
|
|
|
|
## @method boolean setSessionFromDump(Lasso::Profile profile, string dump)
|
|
|
|
# Set session from dump in Lasso::Profile object
|
|
|
|
# @param profile Lasso::Profile object
|
|
|
|
# @param dump Lasso::Session XML dump
|
|
|
|
# @return result
|
|
|
|
sub setSessionFromDump {
|
|
|
|
my ( $self, $profile, $dump ) = @_;
|
|
|
|
|
2017-02-15 07:41:50 +01:00
|
|
|
$self->logger->debug("Loading Session dump: $dump");
|
2016-09-22 22:12:56 +02:00
|
|
|
|
|
|
|
eval { Lasso::Profile::set_session_from_dump( $profile, $dump ); };
|
|
|
|
|
|
|
|
return $self->checkLassoError($@);
|
|
|
|
}
|
|
|
|
|
|
|
|
## @method boolean setIdentityFromDump(Lasso::Profile profile, string dump)
|
|
|
|
# Set identity from dump in Lasso::Profile object
|
|
|
|
# @param profile Lasso::Profile object
|
|
|
|
# @param dump Lasso::Identity XML dump
|
|
|
|
# @return result
|
|
|
|
sub setIdentityFromDump {
|
|
|
|
my ( $self, $profile, $dump ) = @_;
|
|
|
|
|
|
|
|
eval { Lasso::Profile::set_identity_from_dump( $profile, $dump ); };
|
|
|
|
|
|
|
|
return $self->checkLassoError($@);
|
|
|
|
}
|
|
|
|
|
|
|
|
## @method string getMetaDataURL(string key, int index)
|
|
|
|
# Get URL stored in a service metadata configuration key
|
|
|
|
# Replace #PORTAL# macro
|
|
|
|
# @param key Metadata configuration key
|
|
|
|
# @param index field index containing URL
|
|
|
|
# @param full Return full URL instead of path
|
|
|
|
# @return url
|
|
|
|
sub getMetaDataURL {
|
2016-11-16 16:27:01 +01:00
|
|
|
my ( $self, $key, $index, $full ) = @_;
|
2016-09-22 22:12:56 +02:00
|
|
|
$index = 3 unless defined $index;
|
|
|
|
$full = 0 unless defined $full;
|
|
|
|
|
2016-11-20 06:35:03 +01:00
|
|
|
return '' unless defined $self->conf->{$key};
|
2016-09-22 22:12:56 +02:00
|
|
|
|
2016-11-20 06:35:03 +01:00
|
|
|
my $url = ( split( /;/, $self->conf->{$key} ) )[$index] || '';
|
2016-09-22 22:12:56 +02:00
|
|
|
|
|
|
|
# Get portal value
|
|
|
|
my $portal = $self->conf->{portal};
|
|
|
|
$portal =~ s/\/$//;
|
|
|
|
|
|
|
|
# Replace #PORTAL# macro
|
|
|
|
$url =~ s/#PORTAL#/$portal/g;
|
|
|
|
|
|
|
|
# Return Full URL
|
|
|
|
return $url if $full;
|
|
|
|
|
|
|
|
# Return only path
|
|
|
|
my $uri = URI->new($url);
|
|
|
|
return $uri->path();
|
|
|
|
}
|
|
|
|
|
2016-12-18 09:07:48 +01:00
|
|
|
sub getRouteFromMetaDataURL {
|
|
|
|
my ( $self, $key, $index, $sub ) = @_;
|
|
|
|
my $uri = $self->getMetaDataURL( $key, $index, 0 );
|
|
|
|
unless ( $uri =~ m#^/\w# ) {
|
2017-02-15 07:41:50 +01:00
|
|
|
$self->logger->debug("$key has no index $index");
|
2016-12-19 07:14:44 +01:00
|
|
|
return ();
|
2016-12-18 09:07:48 +01:00
|
|
|
}
|
|
|
|
my @t = grep /\w/, split( /\//, $uri );
|
|
|
|
my $h = { pop(@t) => $sub };
|
|
|
|
while ( my $s = pop @t ) {
|
|
|
|
$h = { $s => $h };
|
|
|
|
}
|
|
|
|
return %$h;
|
|
|
|
}
|
|
|
|
|
2016-12-19 07:14:44 +01:00
|
|
|
sub addRouteFromMetaDataURL {
|
2016-12-18 09:07:48 +01:00
|
|
|
my ( $self, @args ) = @_;
|
2016-12-19 07:14:44 +01:00
|
|
|
$self->addAuthRouteFromMetaDataURL(@args);
|
|
|
|
$self->addUnauthRouteFromMetaDataURL(@args);
|
2016-12-18 09:07:48 +01:00
|
|
|
}
|
|
|
|
|
2016-12-19 07:14:44 +01:00
|
|
|
sub addAuthRouteFromMetaDataURL {
|
2016-12-18 09:07:48 +01:00
|
|
|
my ( $self, $key, $index, $sub, $methods ) = @_;
|
|
|
|
my %route = $self->getRouteFromMetaDataURL( $key, $index, $sub );
|
|
|
|
return unless (%route);
|
|
|
|
$self->addAuthRoute( %route, $methods );
|
|
|
|
}
|
|
|
|
|
2016-12-19 07:14:44 +01:00
|
|
|
sub addUnauthRouteFromMetaDataURL {
|
2016-12-18 09:07:48 +01:00
|
|
|
my ( $self, $key, $index, $sub, $methods ) = @_;
|
|
|
|
my %route = $self->getRouteFromMetaDataURL( $key, $index, $sub );
|
|
|
|
return unless (%route);
|
|
|
|
$self->addUnauthRoute( %route, $methods );
|
|
|
|
}
|
|
|
|
|
2016-09-22 22:12:56 +02:00
|
|
|
## @method boolean processLogoutResponseMsg(Lasso::Logout logout, string response)
|
|
|
|
# Process logout response message
|
|
|
|
# @param logout Lasso::Logout object
|
|
|
|
# @param response SAML response
|
|
|
|
# @return result
|
|
|
|
sub processLogoutResponseMsg {
|
|
|
|
my ( $self, $logout, $response ) = @_;
|
|
|
|
|
|
|
|
eval { Lasso::Logout::process_response_msg( $logout, $response ); };
|
|
|
|
|
|
|
|
return $self->checkLassoError($@);
|
|
|
|
}
|
|
|
|
|
|
|
|
## @method boolean processLogoutRequestMsg(Lasso::Logout logout, string request)
|
|
|
|
# Process logout request message
|
|
|
|
# @param logout Lasso::Logout object
|
|
|
|
# @param request SAML request
|
|
|
|
# @return result
|
|
|
|
sub processLogoutRequestMsg {
|
|
|
|
my ( $self, $logout, $request ) = @_;
|
|
|
|
|
|
|
|
# Process the request
|
|
|
|
eval { Lasso::Logout::process_request_msg( $logout, $request ); };
|
|
|
|
|
|
|
|
return 0 unless $self->checkLassoError($@);
|
|
|
|
|
|
|
|
# Check NotOnOrAfter optional attribute
|
|
|
|
my $notOnOrAfter;
|
|
|
|
|
|
|
|
eval { $notOnOrAfter = $logout->request()->NotOnOrAfter(); };
|
|
|
|
|
|
|
|
return 1 if ( $@ or !$notOnOrAfter );
|
|
|
|
|
2017-02-15 07:41:50 +01:00
|
|
|
$self->logger->debug("Found NotOnOrAfter $notOnOrAfter in logout request");
|
2016-09-22 22:12:56 +02:00
|
|
|
|
|
|
|
my $expirationTime = $self->samldate2timestamp($notOnOrAfter);
|
|
|
|
|
2018-01-15 15:08:44 +01:00
|
|
|
return ( time < $expirationTime );
|
2016-09-22 22:12:56 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
## @method boolean validateLogoutRequest(Lasso::Logout logout)
|
|
|
|
# Validate logout request
|
|
|
|
# @param logout Lasso::Logout object
|
|
|
|
# @return result
|
|
|
|
sub validateLogoutRequest {
|
|
|
|
my ( $self, $logout ) = @_;
|
|
|
|
|
|
|
|
eval { Lasso::Logout::validate_request($logout); };
|
|
|
|
|
|
|
|
return $self->checkLassoError($@);
|
|
|
|
}
|
|
|
|
|
|
|
|
## @method boolean buildLogoutResponseMsg(Lasso::Logout logout)
|
|
|
|
# Build logout response message
|
|
|
|
# @param logout Lasso::Logout object
|
|
|
|
# @return boolean result
|
|
|
|
sub buildLogoutResponseMsg {
|
|
|
|
my ( $self, $logout ) = @_;
|
|
|
|
|
|
|
|
eval { Lasso::Logout::build_response_msg($logout); };
|
|
|
|
|
|
|
|
return $self->checkLassoError($@);
|
|
|
|
}
|
|
|
|
|
|
|
|
## @method boolean storeReplayProtection(string samlID, string samlData)
|
|
|
|
# Store ID of an SAML message in Replay Protection base
|
|
|
|
# @param samlID ID of SAML message
|
|
|
|
# @param samlData Optional data to store
|
|
|
|
# @return result
|
|
|
|
sub storeReplayProtection {
|
|
|
|
my ( $self, $samlID, $samlData ) = @_;
|
2017-02-20 22:00:05 +01:00
|
|
|
my $infos = {
|
|
|
|
type => 'assertion', # Session type
|
|
|
|
_utime => time(), # Creation time
|
|
|
|
_assert_id => $samlID,
|
|
|
|
};
|
2016-09-22 22:12:56 +02:00
|
|
|
|
|
|
|
if ( defined $samlData && $samlData ) {
|
|
|
|
$infos->{data} = $samlData;
|
|
|
|
}
|
|
|
|
|
2017-02-20 22:00:05 +01:00
|
|
|
my $samlSessionInfo = $self->getSamlSession( undef, $infos );
|
|
|
|
return 0 unless $samlSessionInfo;
|
2016-09-22 22:12:56 +02:00
|
|
|
|
|
|
|
my $session_id = $samlSessionInfo->id;
|
|
|
|
|
2017-02-15 07:41:50 +01:00
|
|
|
$self->logger->debug(
|
|
|
|
"Keep request ID $samlID in assertion session $session_id");
|
2016-09-22 22:12:56 +02:00
|
|
|
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
## @method boolean replayProtection(string samlID)
|
|
|
|
# Check if SAML message do not correspond to a previously responded message
|
|
|
|
# @param samlID ID of initial SAML message
|
|
|
|
# @return result
|
|
|
|
sub replayProtection {
|
|
|
|
my ( $self, $samlID ) = @_;
|
|
|
|
|
|
|
|
unless ($samlID) {
|
2017-02-15 07:41:50 +01:00
|
|
|
$self->userLogger->error(
|
|
|
|
"Cannot verify replay because no SAML ID given");
|
2016-09-22 22:12:56 +02:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2017-09-27 07:00:00 +02:00
|
|
|
my $sessions =
|
|
|
|
Lemonldap::NG::Common::Apache::Session->searchOn( $self->amOpts,
|
|
|
|
"_assert_id", $samlID );
|
2016-09-22 22:12:56 +02:00
|
|
|
|
2018-06-29 06:50:31 +02:00
|
|
|
if (
|
|
|
|
my @keys =
|
|
|
|
grep { $sessions->{$_}->{_session_kind} eq $self->sessionKind }
|
|
|
|
keys %$sessions
|
|
|
|
)
|
|
|
|
{
|
2016-09-22 22:12:56 +02:00
|
|
|
|
|
|
|
# A session was found
|
|
|
|
foreach (@keys) {
|
2018-06-29 06:50:31 +02:00
|
|
|
next
|
|
|
|
unless ( $sessions->{$_}->{_session_kind} eq $self->sessionKind );
|
2016-09-22 22:12:56 +02:00
|
|
|
my $session = $_;
|
|
|
|
my $result = 1;
|
|
|
|
|
|
|
|
# Delete it
|
|
|
|
my $samlSessionInfo = $self->getSamlSession($_);
|
|
|
|
|
|
|
|
return 0 unless $samlSessionInfo;
|
|
|
|
|
|
|
|
if ( defined $samlSessionInfo->data->{data} ) {
|
|
|
|
$result = $samlSessionInfo->data->{data};
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( $samlSessionInfo->remove ) {
|
2017-02-15 07:41:50 +01:00
|
|
|
$self->logger->debug(
|
|
|
|
"Assertion session $session (Message ID $samlID) was deleted"
|
2016-09-22 22:12:56 +02:00
|
|
|
);
|
|
|
|
return $result;
|
|
|
|
}
|
|
|
|
else {
|
2017-02-15 07:41:50 +01:00
|
|
|
$self->logger->error(
|
|
|
|
"Unable to delete assertion session $session (Message ID $samlID)"
|
2016-09-22 22:12:56 +02:00
|
|
|
);
|
2017-02-15 07:41:50 +01:00
|
|
|
$self->logger->error( $samlSessionInfo->error );
|
2016-09-22 22:12:56 +02:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
}
|
2020-05-24 00:04:33 +02:00
|
|
|
}
|
|
|
|
else {
|
|
|
|
$self->logger->warn(
|
|
|
|
"No assertion session found for request ID " . $samlID );
|
2016-09-22 22:12:56 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
## @method string resolveArtifact(Lasso::Profile profile, string artifact, int method)
|
|
|
|
# Resolve artifact to get real SAML message
|
|
|
|
# @param profile Lasso::Profile object
|
|
|
|
# @param artifact Artifact message
|
|
|
|
# @param method HTTP method
|
|
|
|
# @return SAML message
|
|
|
|
sub resolveArtifact {
|
|
|
|
my ( $self, $profile, $artifact, $method ) = @_;
|
|
|
|
my $message;
|
|
|
|
|
|
|
|
# Login profile
|
|
|
|
if ( $profile->isa("Lasso::Login") ) {
|
|
|
|
|
|
|
|
# Init request message
|
|
|
|
eval { Lasso::Login::init_request( $profile, $artifact, $method ); };
|
|
|
|
return unless $self->checkLassoError($@);
|
|
|
|
|
|
|
|
# Build request message
|
|
|
|
eval { Lasso::Login::build_request_msg($profile); };
|
|
|
|
return unless $self->checkLassoError($@);
|
|
|
|
|
|
|
|
unless ( $profile->msg_url ) {
|
2017-02-15 07:41:50 +01:00
|
|
|
$self->logger->error("No artifact resolution URL found");
|
2016-09-22 22:12:56 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
my $request = HTTP::Request->new( 'POST' => $profile->msg_url );
|
2020-07-24 14:03:48 +02:00
|
|
|
$request->content_type('text/xml');
|
|
|
|
$request->header( Accept => 'text/xml' );
|
2016-09-22 22:12:56 +02:00
|
|
|
$request->content( $profile->msg_body );
|
|
|
|
|
2017-02-15 07:41:50 +01:00
|
|
|
$self->logger->debug(
|
|
|
|
"Send message " . $profile->msg_body . " to " . $profile->msg_url );
|
2016-09-22 22:12:56 +02:00
|
|
|
|
|
|
|
# SOAP call
|
2016-11-29 22:44:33 +01:00
|
|
|
my $soap_answer = $self->ua->request($request);
|
2016-09-22 22:12:56 +02:00
|
|
|
if ( $soap_answer->code() == "200" ) {
|
|
|
|
$message = $soap_answer->content();
|
2017-02-15 07:41:50 +01:00
|
|
|
$self->logger->debug("Get message $message");
|
2016-09-22 22:12:56 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return $message;
|
|
|
|
}
|
|
|
|
|
|
|
|
## @method boolean storeArtifact(string id, string message, string session_id)
|
|
|
|
# Store artifact
|
|
|
|
# @param id Artifact ID
|
|
|
|
# @param message Artifact content
|
|
|
|
# @param session_id Session ID
|
|
|
|
# @return result
|
|
|
|
sub storeArtifact {
|
|
|
|
my ( $self, $id, $message, $session_id ) = @_;
|
|
|
|
|
2017-02-20 22:00:05 +01:00
|
|
|
my $infos = {
|
|
|
|
type => 'artifact', # Session type
|
|
|
|
_utime => time(), # Creation time
|
|
|
|
_art_id => $id,
|
|
|
|
message => $message,
|
|
|
|
};
|
2016-09-22 22:12:56 +02:00
|
|
|
$infos->{_saml_id} = $session_id if $session_id;
|
|
|
|
|
2017-02-20 22:00:05 +01:00
|
|
|
my $samlSessionInfo = $self->getSamlSession( undef, $infos ) or return 0;
|
|
|
|
return 0 unless $samlSessionInfo;
|
2016-09-22 22:12:56 +02:00
|
|
|
|
|
|
|
my $art_session_id = $samlSessionInfo->id;
|
|
|
|
|
2017-02-15 07:41:50 +01:00
|
|
|
$self->logger->debug("Keep artifact $id in session $art_session_id");
|
2016-09-22 22:12:56 +02:00
|
|
|
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
## @method hashRef loadArtifact(string id)
|
|
|
|
# Load artifact
|
|
|
|
# @param id Artifact ID
|
|
|
|
# @return Artifact session content
|
|
|
|
sub loadArtifact {
|
|
|
|
my ( $self, $id ) = @_;
|
|
|
|
my $art_session;
|
|
|
|
|
|
|
|
unless ($id) {
|
2017-02-15 07:41:50 +01:00
|
|
|
$self->logger->error("Cannot load artifact because no id given");
|
2016-09-22 22:12:56 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2017-09-27 07:00:00 +02:00
|
|
|
my $sessions =
|
|
|
|
Lemonldap::NG::Common::Apache::Session->searchOn( $self->amOpts,
|
|
|
|
"_art_id", $id );
|
2016-09-22 22:12:56 +02:00
|
|
|
|
2018-06-29 06:50:31 +02:00
|
|
|
if (
|
|
|
|
my @keys =
|
|
|
|
grep { $sessions->{$_}->{_session_kind} eq $self->sessionKind }
|
|
|
|
keys %$sessions
|
|
|
|
)
|
|
|
|
{
|
2016-09-22 22:12:56 +02:00
|
|
|
|
|
|
|
my $nb_sessions = $#keys + 1;
|
|
|
|
|
2017-02-15 07:41:50 +01:00
|
|
|
$self->logger->debug("Found $nb_sessions sessions for artifact $id");
|
2016-09-22 22:12:56 +02:00
|
|
|
|
|
|
|
# There should only be 1 result
|
|
|
|
return if ( $nb_sessions != 1 );
|
|
|
|
|
|
|
|
my $session_id = shift @keys;
|
|
|
|
my $session = $session_id;
|
|
|
|
|
|
|
|
# Open session
|
|
|
|
my $samlSessionInfo = $self->getSamlSession($session_id);
|
|
|
|
|
|
|
|
return unless $samlSessionInfo;
|
|
|
|
|
|
|
|
# Get session contents
|
|
|
|
foreach ( keys %{ $samlSessionInfo->data } ) {
|
|
|
|
$art_session->{$_} = $samlSessionInfo->data->{$_};
|
|
|
|
}
|
|
|
|
|
|
|
|
# Delete session
|
|
|
|
if ( $samlSessionInfo->remove ) {
|
2017-02-15 07:41:50 +01:00
|
|
|
$self->logger->debug(
|
|
|
|
"Artifact session $session (ID $id) was deleted");
|
2016-09-22 22:12:56 +02:00
|
|
|
|
|
|
|
return $art_session;
|
|
|
|
}
|
|
|
|
else {
|
2017-02-15 07:41:50 +01:00
|
|
|
$self->logger->error(
|
|
|
|
"Unable to delete artifact session $session (ID $id)");
|
|
|
|
$self->logger->error( $samlSessionInfo->error );
|
2016-09-22 22:12:56 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
## @method string createArtifactResponse(Lasso::Login login)
|
|
|
|
# Create artifact response
|
|
|
|
# @param login Lasso::Login object
|
|
|
|
# @return Artifact response
|
|
|
|
sub createArtifactResponse {
|
2016-12-15 06:55:09 +01:00
|
|
|
my ( $self, $req, $login ) = @_;
|
2016-09-22 22:12:56 +02:00
|
|
|
|
|
|
|
my $artifact_id = $login->assertionArtifact();
|
|
|
|
|
|
|
|
# Load artifact message into login response
|
|
|
|
my $art_session = $self->loadArtifact($artifact_id);
|
2017-03-03 18:25:03 +01:00
|
|
|
utf8::decode( $art_session->{message} );
|
2016-09-22 22:12:56 +02:00
|
|
|
eval { $login->set_artifact_message( $art_session->{message} ); };
|
|
|
|
if ($@) {
|
|
|
|
$self->checkLassoError($@);
|
2017-02-15 07:41:50 +01:00
|
|
|
$self->logger->error("Cannot load artifact message");
|
2016-09-22 22:12:56 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2017-02-15 07:41:50 +01:00
|
|
|
$self->logger->debug("Response loaded");
|
2016-09-22 22:12:56 +02:00
|
|
|
|
|
|
|
# Try to get Lasso session
|
|
|
|
my $session_id = $art_session->{_saml_id};
|
|
|
|
if ($session_id) {
|
2017-02-15 07:41:50 +01:00
|
|
|
$self->logger->debug("Find session_id $session_id in artifact session");
|
2016-09-22 22:12:56 +02:00
|
|
|
|
2017-01-24 06:10:57 +01:00
|
|
|
my $session = $self->p->getApacheSession($session_id);
|
2016-09-22 22:12:56 +02:00
|
|
|
unless ($session) {
|
2017-02-15 07:41:50 +01:00
|
|
|
$self->logger->error("Unable to open session $session_id");
|
2016-09-22 22:12:56 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2018-06-29 06:50:31 +02:00
|
|
|
my $lassoSession = $session->data->{ $self->lsDump };
|
2016-09-22 22:12:56 +02:00
|
|
|
|
|
|
|
if ($lassoSession) {
|
|
|
|
unless ( $self->setSessionFromDump( $login, $lassoSession ) ) {
|
2017-02-15 07:41:50 +01:00
|
|
|
$self->logger->error("Unable to load Lasso Session");
|
2016-09-22 22:12:56 +02:00
|
|
|
return;
|
|
|
|
}
|
2017-02-15 07:41:50 +01:00
|
|
|
$self->logger->debug("Lasso Session loaded");
|
2016-09-22 22:12:56 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
else {
|
2017-02-15 07:41:50 +01:00
|
|
|
$self->logger->debug("No session_id in artifact session");
|
2016-09-22 22:12:56 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
# Build artifact response
|
|
|
|
eval { Lasso::Login::build_response_msg($login); };
|
|
|
|
if ($@) {
|
|
|
|
$self->checkLassoError($@);
|
2017-02-15 07:41:50 +01:00
|
|
|
$self->logger->error("Cannot build artifact response");
|
2016-09-22 22:12:56 +02:00
|
|
|
return;
|
|
|
|
}
|
2017-02-15 07:41:50 +01:00
|
|
|
$self->logger->debug("Artifact response built");
|
2016-09-22 22:12:56 +02:00
|
|
|
|
|
|
|
# Store Lasso session if session opened
|
|
|
|
if ( $session_id and $login->is_session_dirty ) {
|
2017-02-15 07:41:50 +01:00
|
|
|
$self->logger->debug("Save Lasso session in session");
|
2016-12-15 06:55:09 +01:00
|
|
|
$self->updateSession( $req,
|
2018-06-29 06:50:31 +02:00
|
|
|
{ $self->lsDump => $login->get_session->dump }, $session_id );
|
2016-09-22 22:12:56 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
# Return artifact message
|
|
|
|
return $login->msg_body;
|
|
|
|
}
|
|
|
|
|
|
|
|
## @method boolean processArtRequestMsg(Lasso::Profile profile, string request)
|
|
|
|
# Process artifact request message
|
|
|
|
# @param profile Lasso::Profile object
|
|
|
|
# @param request SAML request
|
|
|
|
# @return result
|
|
|
|
sub processArtRequestMsg {
|
|
|
|
my ( $self, $profile, $request ) = @_;
|
|
|
|
|
|
|
|
# Login profile
|
|
|
|
if ( $profile->isa("Lasso::Login") ) {
|
|
|
|
|
|
|
|
eval { Lasso::Login::process_request_msg( $profile, $request ); };
|
|
|
|
return $self->checkLassoError($@);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
## @method boolean processArtResponseMsg(Lasso::Profile profile, string response)
|
|
|
|
# Process artifact response message
|
|
|
|
# @param profile Lasso::Profile object
|
|
|
|
# @param response SAML response
|
|
|
|
# @return result
|
|
|
|
sub processArtResponseMsg {
|
|
|
|
my ( $self, $profile, $response ) = @_;
|
|
|
|
|
|
|
|
# Login profile
|
|
|
|
if ( $profile->isa("Lasso::Login") ) {
|
|
|
|
|
|
|
|
eval { Lasso::Login::process_response_msg( $profile, $response ); };
|
|
|
|
return $self->checkLassoError($@);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
## @method string sendSOAPMessage(string endpoint, string message)
|
|
|
|
# Send SOAP message and get response
|
|
|
|
# @param endpoint SOAP End Point
|
|
|
|
# @param message SOAP message
|
|
|
|
# @return SOAP response
|
|
|
|
sub sendSOAPMessage {
|
|
|
|
my ( $self, $endpoint, $message ) = @_;
|
|
|
|
my $response;
|
|
|
|
|
|
|
|
my $request = HTTP::Request->new( 'POST' => $endpoint );
|
2020-07-24 14:03:48 +02:00
|
|
|
$request->content_type('text/xml');
|
|
|
|
$request->header( Accept => 'text/xml' );
|
2016-09-22 22:12:56 +02:00
|
|
|
$request->content($message);
|
|
|
|
|
2017-02-15 07:41:50 +01:00
|
|
|
$self->logger->debug("Send SOAP message $message to $endpoint");
|
2016-09-22 22:12:56 +02:00
|
|
|
|
|
|
|
# SOAP call
|
|
|
|
my $soap_answer = $self->ua()->request($request);
|
|
|
|
if ( $soap_answer->code() == "200" ) {
|
|
|
|
$response = $soap_answer->content();
|
2017-02-15 07:41:50 +01:00
|
|
|
$self->logger->debug("Get response $response");
|
2016-09-22 22:12:56 +02:00
|
|
|
}
|
|
|
|
else {
|
2017-02-15 07:41:50 +01:00
|
|
|
$self->logger->debug("No response to SOAP request");
|
2016-09-22 22:12:56 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
return $response;
|
|
|
|
}
|
|
|
|
|
|
|
|
## @method Lasso::AssertionQuery createAssertionQuery(Lasso::Server server)
|
|
|
|
# Create a new assertion query
|
|
|
|
# @param server Lasso::Server object
|
|
|
|
# @return assertion query
|
|
|
|
sub createAssertionQuery {
|
|
|
|
my ( $self, $server ) = @_;
|
|
|
|
my $query;
|
|
|
|
|
|
|
|
# Create assertion query
|
|
|
|
eval { $query = Lasso::AssertionQuery->new($server); };
|
|
|
|
if ($@) {
|
|
|
|
$self->checkLassoError($@);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
return $query;
|
|
|
|
}
|
|
|
|
|
|
|
|
## @method Lasso::AssertionQuery createAttributeRequest(Lasso::Server server, string idp, hashref attributes, Lasso::Saml2NameID nameid)
|
|
|
|
# Create an attribute request
|
|
|
|
# @param server Lasso::Server object
|
|
|
|
# @param idp IDP entityID
|
|
|
|
# @param attributes List of requested attributes
|
|
|
|
# @param nameid Subject NameID
|
|
|
|
# @return attribute request
|
|
|
|
sub createAttributeRequest {
|
|
|
|
my ( $self, $server, $idp, $attributes, $nameid ) = @_;
|
|
|
|
my $query;
|
|
|
|
|
|
|
|
# Create assertion query
|
|
|
|
return unless ( $query = $self->createAssertionQuery($server) );
|
|
|
|
|
2017-02-15 07:41:50 +01:00
|
|
|
$self->logger->debug("Assertion query created");
|
2016-09-22 22:12:56 +02:00
|
|
|
|
|
|
|
# Init request
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2017-02-15 07:41:50 +01:00
|
|
|
$self->logger->debug("Assertion query request initiated");
|
2016-09-22 22:12:56 +02:00
|
|
|
|
|
|
|
# Set NameID
|
|
|
|
eval { $query->request()->Subject()->NameID($nameid); };
|
|
|
|
if ($@) {
|
|
|
|
$self->checkLassoError($@);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2017-02-15 07:41:50 +01:00
|
|
|
$self->logger->debug(
|
|
|
|
"Set NameID " . $nameid->dump . " in assertion query" );
|
2016-09-22 22:12:56 +02:00
|
|
|
|
|
|
|
# Store attributes in request
|
|
|
|
my @requested_attributes;
|
|
|
|
foreach ( keys %$attributes ) {
|
|
|
|
|
|
|
|
# Create SAML2 Attribute
|
|
|
|
my $attribute;
|
|
|
|
|
|
|
|
eval { $attribute = Lasso::Saml2Attribute->new(); };
|
|
|
|
if ($@) {
|
|
|
|
$self->checkLassoError($@);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
# Set attribute properties
|
|
|
|
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;
|
|
|
|
|
|
|
|
# Store attribute
|
|
|
|
push @requested_attributes, $attribute;
|
|
|
|
}
|
|
|
|
|
|
|
|
# Set attributes in request
|
|
|
|
eval { $query->request()->Attribute(@requested_attributes); };
|
|
|
|
if ($@) {
|
|
|
|
$self->checkLassoError($@);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
# Build message
|
|
|
|
eval { Lasso::AssertionQuery::build_request_msg($query); };
|
|
|
|
if ($@) {
|
|
|
|
$self->checkLassoError($@);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
# Return query
|
|
|
|
return $query;
|
|
|
|
}
|
|
|
|
|
|
|
|
## @method boolean validateAttributeRequest(Lasso::AssertionQuery query)
|
|
|
|
# Validate an attribute request
|
|
|
|
# @param query Lasso::AssertionQuery object
|
|
|
|
# @return result
|
|
|
|
sub validateAttributeRequest {
|
|
|
|
my ( $self, $query ) = @_;
|
|
|
|
|
|
|
|
eval { Lasso::AssertionQuery::validate_request($query); };
|
|
|
|
|
|
|
|
return $self->checkLassoError($@);
|
|
|
|
}
|
|
|
|
|
|
|
|
## @method Lasso::AssertionQuery processAttributeRequest(Lasso::Server server, string request)
|
|
|
|
# Process an attribute request
|
|
|
|
# @param server Lasso::Server object
|
|
|
|
# @param request Request content
|
|
|
|
# @return assertion query
|
|
|
|
sub processAttributeRequest {
|
|
|
|
my ( $self, $server, $request ) = @_;
|
|
|
|
my $query;
|
|
|
|
|
|
|
|
# Create assertion query
|
|
|
|
return unless ( $query = $self->createAssertionQuery($server) );
|
|
|
|
|
2017-02-15 07:41:50 +01:00
|
|
|
$self->logger->debug("Assertion query created");
|
2016-09-22 22:12:56 +02:00
|
|
|
|
|
|
|
# Process response
|
|
|
|
eval { Lasso::AssertionQuery::process_request_msg( $query, $request ); };
|
|
|
|
if ($@) {
|
|
|
|
$self->checkLassoError($@);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2017-02-15 07:41:50 +01:00
|
|
|
$self->logger->debug("Attribute request is valid");
|
2016-09-22 22:12:56 +02:00
|
|
|
|
|
|
|
return $query;
|
|
|
|
}
|
|
|
|
|
|
|
|
## @method string buildAttributeResponse(Lasso::AssertionQuery query)
|
|
|
|
# Build attribute response
|
|
|
|
# @param query Lasso::AssertionQuery object
|
|
|
|
# @return attribute response
|
|
|
|
sub buildAttributeResponse {
|
|
|
|
my ( $self, $query ) = @_;
|
|
|
|
|
|
|
|
eval { Lasso::AssertionQuery::build_response_msg($query); };
|
|
|
|
|
|
|
|
if ($@) {
|
|
|
|
$self->checkLassoError($@);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
return $query->msg_body;
|
|
|
|
}
|
|
|
|
|
|
|
|
## @method Lasso::AssertionQuery processAttributeResponse(Lasso::Server server, string response)
|
|
|
|
# Process an attribute response
|
|
|
|
# @param server Lasso::Server object
|
|
|
|
# @param response Response content
|
|
|
|
# @return assertion query
|
|
|
|
sub processAttributeResponse {
|
|
|
|
my ( $self, $server, $response ) = @_;
|
|
|
|
my $query;
|
|
|
|
|
|
|
|
# Create assertion query
|
|
|
|
return unless ( $query = $self->createAssertionQuery($server) );
|
|
|
|
|
2017-02-15 07:41:50 +01:00
|
|
|
$self->logger->debug("Assertion query created");
|
2016-09-22 22:12:56 +02:00
|
|
|
|
|
|
|
# Process response
|
|
|
|
eval { Lasso::AssertionQuery::process_response_msg( $query, $response ); };
|
|
|
|
if ($@) {
|
|
|
|
$self->checkLassoError($@);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2017-02-15 07:41:50 +01:00
|
|
|
$self->logger->debug("Attribute response is valid");
|
2016-09-22 22:12:56 +02:00
|
|
|
|
|
|
|
return $query;
|
|
|
|
}
|
|
|
|
|
|
|
|
## @method string getNameIDFormat(string format)
|
|
|
|
# Convert configuration string into SAML2 NameIDFormat string
|
|
|
|
# @param format configuration string
|
|
|
|
# @return SAML2 NameIDFormat string
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
|
|
|
## @method int getHttpMethod(string method)
|
|
|
|
# Convert configuration string into Lasso HTTP Method integer
|
|
|
|
# @param method configuration string
|
|
|
|
# @return Lasso HTTP Method integer
|
|
|
|
sub getHttpMethod {
|
|
|
|
my ( $self, $method ) = @_;
|
2019-09-16 20:30:35 +02:00
|
|
|
|
2019-09-16 17:22:35 +02:00
|
|
|
$method ||= '';
|
2016-09-22 22:12:56 +02:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
|
|
|
## @method int getHttpMethodString(int method)
|
|
|
|
# Convert configuration Lasso HTTP Method integer into string
|
|
|
|
# @param method Lasso HTTP Method
|
|
|
|
# @return method string
|
|
|
|
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";
|
|
|
|
}
|
|
|
|
## @method int getFirstHttpMethod(Lasso::Server server, string entityID, int protocolType)
|
|
|
|
# Find a suitable HTTP method for an entity with a given protocol
|
|
|
|
# @param server Lasso::Server object
|
|
|
|
# @param entityID entity ID
|
|
|
|
# @param protocolType Lasso protocol type
|
|
|
|
# @return Lasso HTTP Method
|
|
|
|
sub getFirstHttpMethod {
|
|
|
|
my ( $self, $server, $entityID, $protocolType ) = @_;
|
|
|
|
my $entity_provider;
|
|
|
|
my $method;
|
|
|
|
|
|
|
|
# Get Lasso::Provider object
|
|
|
|
eval {
|
|
|
|
$entity_provider = Lasso::Server::get_provider( $server, $entityID );
|
|
|
|
};
|
|
|
|
if ($@) {
|
|
|
|
$self->checkLassoError($@);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
# Find HTTP method
|
|
|
|
eval {
|
|
|
|
$method =
|
|
|
|
Lasso::Provider::get_first_http_method( $server, $entity_provider,
|
|
|
|
$protocolType );
|
|
|
|
};
|
|
|
|
if ($@) {
|
|
|
|
$self->checkLassoError($@);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
return $method;
|
|
|
|
}
|
|
|
|
|
|
|
|
## @method boolean disableSignature(Lasso::Profile profile)
|
|
|
|
# Modify Lasso signature hint to disable signature
|
|
|
|
# @param profile Lasso profile object
|
|
|
|
# @return result
|
|
|
|
sub disableSignature {
|
|
|
|
my ( $self, $profile ) = @_;
|
|
|
|
|
|
|
|
eval {
|
|
|
|
Lasso::Profile::set_signature_hint( $profile,
|
|
|
|
Lasso::Constants::PROFILE_SIGNATURE_HINT_FORBID );
|
|
|
|
};
|
|
|
|
|
|
|
|
return $self->checkLassoError($@);
|
|
|
|
}
|
|
|
|
|
|
|
|
## @method boolean forceSignature(Lasso::Profile profile)
|
|
|
|
# Modify Lasso signature hint to force signature
|
|
|
|
# @param profile Lasso profile object
|
|
|
|
# @return result
|
|
|
|
sub forceSignature {
|
|
|
|
my ( $self, $profile ) = @_;
|
|
|
|
|
|
|
|
eval {
|
|
|
|
Lasso::Profile::set_signature_hint( $profile,
|
|
|
|
Lasso::Constants::PROFILE_SIGNATURE_HINT_FORCE );
|
|
|
|
};
|
|
|
|
|
|
|
|
return $self->checkLassoError($@);
|
|
|
|
}
|
|
|
|
|
|
|
|
## @method boolean disableSignatureVerification(Lasso::Profile profile)
|
|
|
|
# Modify Lasso signature hint to disable signature verification
|
|
|
|
# @param profile Lasso profile object
|
|
|
|
# @return result
|
|
|
|
sub disableSignatureVerification {
|
|
|
|
my ( $self, $profile ) = @_;
|
|
|
|
|
|
|
|
eval {
|
|
|
|
Lasso::Profile::set_signature_verify_hint( $profile,
|
|
|
|
Lasso::Constants::PROFILE_SIGNATURE_VERIFY_HINT_IGNORE );
|
|
|
|
};
|
|
|
|
|
|
|
|
return $self->checkLassoError($@);
|
|
|
|
}
|
|
|
|
|
|
|
|
## @method boolean forceSignatureVerification(Lasso::Profile profile)
|
|
|
|
# Modify Lasso signature hint to force signature verification
|
|
|
|
# @param profile Lasso profile object
|
|
|
|
# @return result
|
|
|
|
sub forceSignatureVerification {
|
|
|
|
my ( $self, $profile ) = @_;
|
|
|
|
|
|
|
|
eval {
|
|
|
|
Lasso::Profile::set_signature_verify_hint( $profile,
|
2016-12-05 12:19:29 +01:00
|
|
|
Lasso::Constants::PROFILE_SIGNATURE_VERIFY_HINT_MAYBE );
|
2016-09-22 22:12:56 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
return $self->checkLassoError($@);
|
|
|
|
}
|
|
|
|
|
|
|
|
## @method string getAuthnContext(string context)
|
|
|
|
# Convert configuration string into SAML2 AuthnContextClassRef string
|
|
|
|
# @param context configuration string
|
|
|
|
# @return SAML2 AuthnContextClassRef string
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
|
|
|
## @method string timestamp2samldate(string timestamp)
|
|
|
|
# Convert timestamp into SAML2 date format
|
|
|
|
# @param timestamp UNIX timestamp
|
|
|
|
# @return SAML2 date
|
|
|
|
sub timestamp2samldate {
|
|
|
|
my ( $self, $timestamp ) = @_;
|
|
|
|
|
2019-07-02 20:03:40 +02:00
|
|
|
my @t = gmtime($timestamp);
|
2016-09-22 22:12:56 +02:00
|
|
|
my $samldate = strftime( "%Y-%m-%dT%TZ", @t );
|
|
|
|
|
2017-02-15 07:41:50 +01:00
|
|
|
$self->logger->debug(
|
|
|
|
"Convert timestamp $timestamp in SAML2 date: $samldate");
|
2016-09-22 22:12:56 +02:00
|
|
|
|
|
|
|
return $samldate;
|
|
|
|
}
|
|
|
|
|
|
|
|
## @method string samldate2timestamp(string samldate)
|
|
|
|
# Convert SAML2 date format into timestamp
|
|
|
|
# @param samldate SAML2 date format
|
|
|
|
# @return UNIX timestamp
|
|
|
|
sub samldate2timestamp {
|
|
|
|
my ( $self, $samldate ) = @_;
|
|
|
|
|
2017-02-15 07:41:50 +01:00
|
|
|
my ( $year, $mon, $mday, $hour, $min, $sec, $msec, $ztime ) = ( $samldate =~
|
2016-09-22 22:12:56 +02:00
|
|
|
/(\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 );
|
|
|
|
|
2017-02-15 07:41:50 +01:00
|
|
|
$self->logger->debug(
|
|
|
|
"Convert SAML2 date $samldate in timestamp: $timestamp");
|
2016-09-22 22:12:56 +02:00
|
|
|
|
|
|
|
return $timestamp;
|
|
|
|
}
|
|
|
|
|
|
|
|
## @pmethod int sendLogoutResponseToServiceProvider(Lasso::Logout $logout, int $method)
|
|
|
|
# Send logout response issue from a logout request.
|
|
|
|
# @param $logout Lasso Logout object
|
|
|
|
# @param $method Method to use
|
|
|
|
# @return boolean False if failed.
|
|
|
|
sub sendLogoutResponseToServiceProvider {
|
|
|
|
my ( $self, $req, $logout, $method ) = @_;
|
|
|
|
|
|
|
|
# Logout response
|
|
|
|
unless ( $self->buildLogoutResponseMsg($logout) ) {
|
2019-04-02 22:38:24 +02:00
|
|
|
$self->logger->warn( "Could not build a logout response for provider "
|
|
|
|
. $logout->remote_providerID
|
|
|
|
. ", staying on portal" );
|
2019-11-06 18:38:58 +01:00
|
|
|
return $self->p->do( $req, [ sub { PE_LOGOUT_OK } ] );
|
2016-09-22 22:12:56 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
# Send response depending on request method
|
|
|
|
# HTTP-REDIRECT
|
|
|
|
if ( $method == Lasso::Constants::HTTP_METHOD_REDIRECT ) {
|
|
|
|
|
|
|
|
# Redirect user to response URL
|
|
|
|
my $slo_url = $logout->msg_url;
|
2016-12-25 16:41:23 +01:00
|
|
|
return [ 302, [ Location => $slo_url ], [] ];
|
2016-09-22 22:12:56 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
# HTTP-POST
|
|
|
|
elsif ( $method == Lasso::Constants::HTTP_METHOD_POST ) {
|
|
|
|
|
|
|
|
# Use autosubmit form
|
|
|
|
my $slo_url = $logout->msg_url;
|
|
|
|
my $slo_body = $logout->msg_body;
|
|
|
|
my $relaystate = $logout->msg_relayState;
|
|
|
|
|
|
|
|
# TODO: insert postUrl in $req
|
|
|
|
$req->postUrl($slo_url);
|
2016-12-15 22:22:15 +01:00
|
|
|
$req->{postFields} = { 'SAMLResponse' => $slo_body };
|
2016-09-22 22:12:56 +02:00
|
|
|
|
|
|
|
# RelayState
|
2016-12-15 22:22:15 +01:00
|
|
|
$req->{postFields}->{'RelayState'} = $relaystate
|
2016-09-22 22:12:56 +02:00
|
|
|
if ($relaystate);
|
|
|
|
|
2016-12-25 16:41:23 +01:00
|
|
|
return $self->p->do( $req, ['autoPost'] );
|
2016-09-22 22:12:56 +02:00
|
|
|
}
|
2016-12-25 16:41:28 +01:00
|
|
|
elsif ( $method == Lasso::Constants::HTTP_METHOD_SOAP ) {
|
|
|
|
return $self->sendSLOSoapErrorResponse( $req, $logout, $method );
|
|
|
|
}
|
2016-12-25 16:41:23 +01:00
|
|
|
return $self->p->sendError( $req,
|
|
|
|
"Lasso method '$method' should not be handle here...", 400 );
|
2016-09-22 22:12:56 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
## @pmethod int sendLogoutRequestToProvider(Lasso::Logout $logout, string $providerID, int $method, boolean $relay, string $relayState)
|
|
|
|
# Send a logout request to a provider
|
|
|
|
# If information have to be displayed to users, such as iframe to send
|
|
|
|
# HTTP-Redirect or HTTP-POST logout request, then $self->{_info} will be
|
|
|
|
# updated.
|
|
|
|
# @param $logout Lasso Logout object
|
|
|
|
# @param $providerID The concerned service provider
|
|
|
|
# @param $method The method used to send the logout request
|
|
|
|
# @param $relay If SOAP method, build a relay logout request
|
|
|
|
# @param $relayState Relay State for SLO status
|
|
|
|
# @return int Number of concerned providers.
|
|
|
|
sub sendLogoutRequestToProvider {
|
|
|
|
my ( $self, $req, $logout, $providerID, $method, $relay, $relayState ) = @_;
|
|
|
|
my $server = $self->lassoServer;
|
|
|
|
my $info;
|
|
|
|
|
|
|
|
# Test if provider is mentionned
|
|
|
|
if ( !$providerID ) {
|
|
|
|
return ( 0, undef, undef );
|
|
|
|
}
|
|
|
|
|
|
|
|
my $type = defined $self->spList->{$providerID} ? "SP" : "IDP";
|
|
|
|
|
|
|
|
# Find EntityID in spList or idpList
|
|
|
|
unless ( defined $self->{ lc($type) . 'List' }->{$providerID} ) {
|
2017-02-15 07:41:50 +01:00
|
|
|
$self->logger->error("$providerID does not match any known $type");
|
2016-09-22 22:12:56 +02:00
|
|
|
return ( 0, undef, undef );
|
|
|
|
}
|
|
|
|
|
|
|
|
# Get Provider Name and Conf Key from EntityID
|
|
|
|
my $providerName =
|
|
|
|
$self->{ lc($type) . 'List' }->{$providerID}->{name};
|
|
|
|
my $confKey =
|
|
|
|
$self->{ lc($type) . 'List' }->{$providerID}->{confKey};
|
|
|
|
|
|
|
|
# Get first HTTP method
|
|
|
|
my $protocolType = Lasso::Constants::MD_PROTOCOL_TYPE_SINGLE_LOGOUT;
|
|
|
|
if ( !$method ) {
|
|
|
|
$method =
|
|
|
|
$self->getFirstHttpMethod( $server, $providerID, $protocolType );
|
|
|
|
}
|
|
|
|
|
|
|
|
# Fix a default value for the relay parameter
|
|
|
|
$relay = 0 unless ( defined $relay );
|
|
|
|
|
|
|
|
# Signature
|
|
|
|
my $signSLOMessage =
|
|
|
|
$self->conf->{ 'saml' . $type . 'MetaDataOptions' }->{$confKey}
|
|
|
|
->{ 'saml' . $type . 'MetaDataOptionsSignSLOMessage' };
|
|
|
|
|
|
|
|
if ( $signSLOMessage == 0 ) {
|
2017-02-15 07:41:50 +01:00
|
|
|
$self->logger->debug("SLO request will not be signed");
|
2016-09-22 22:12:56 +02:00
|
|
|
$self->disableSignature($logout);
|
|
|
|
}
|
|
|
|
elsif ( $signSLOMessage == 1 ) {
|
2017-02-15 07:41:50 +01:00
|
|
|
$self->logger->debug("SLO request will be signed");
|
2016-09-22 22:12:56 +02:00
|
|
|
$self->forceSignature($logout);
|
|
|
|
}
|
|
|
|
else {
|
2017-02-15 07:41:50 +01:00
|
|
|
$self->logger->debug("SLO request signature according to metadata");
|
2016-09-22 22:12:56 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
# Relay State
|
|
|
|
if ($relayState) {
|
|
|
|
eval { $logout->msg_relayState($relayState); };
|
|
|
|
if ($@) {
|
2017-02-15 07:41:50 +01:00
|
|
|
$self->logger->error(
|
|
|
|
"Unable to set Relay State $relayState in SLO request for $confKey"
|
2016-09-22 22:12:56 +02:00
|
|
|
);
|
|
|
|
return ( 0, $method, undef );
|
|
|
|
}
|
2017-02-15 07:41:50 +01:00
|
|
|
$self->logger->debug('Relay state set');
|
2016-09-22 22:12:56 +02:00
|
|
|
}
|
|
|
|
|
2021-01-27 22:10:12 +01:00
|
|
|
# Initiate the logout request
|
|
|
|
unless ( $self->initLogoutRequest( $logout, $providerID, $method ) ) {
|
|
|
|
$self->logger->error("Initiate logout request failed for $providerID");
|
|
|
|
return ( 0, $method, undef );
|
|
|
|
}
|
2016-09-22 22:12:56 +02:00
|
|
|
|
2021-01-27 22:10:12 +01:00
|
|
|
# Build request message
|
|
|
|
unless ( $self->buildLogoutRequestMsg($logout) ) {
|
|
|
|
$self->logger->error("Build logout request failed for $providerID");
|
|
|
|
return ( 0, $method, undef );
|
2016-09-22 22:12:56 +02:00
|
|
|
}
|
|
|
|
|
2021-01-27 22:10:12 +01:00
|
|
|
$self->logger->debug("Request built for $providerID");
|
|
|
|
|
2016-09-22 22:12:56 +02:00
|
|
|
# Keep message ID in memory to prevent replay
|
|
|
|
my $samlID = $logout->request()->ID;
|
|
|
|
unless ( $self->storeReplayProtection($samlID) ) {
|
2017-02-15 07:41:50 +01:00
|
|
|
$self->logger->error("Unable to store message ID");
|
2016-09-22 22:12:56 +02:00
|
|
|
return ( 0, $method, undef );
|
|
|
|
}
|
|
|
|
|
|
|
|
# Get portal value
|
|
|
|
my $portal = $self->conf->{portal};
|
|
|
|
$portal =~ s/\/$//;
|
|
|
|
|
|
|
|
# Send logout request to the provider depending of the request method
|
|
|
|
# HTTP-REDIRECT
|
|
|
|
if ( $method == Lasso::Constants::HTTP_METHOD_REDIRECT ) {
|
|
|
|
|
2017-02-15 07:41:50 +01:00
|
|
|
$self->logger->debug(
|
|
|
|
"Send HTTP-REDIRECT logout request to $providerID");
|
2016-09-22 22:12:56 +02:00
|
|
|
|
|
|
|
# Redirect user to response URL
|
|
|
|
my $slo_url = $logout->msg_url;
|
|
|
|
|
2017-10-11 13:35:56 +02:00
|
|
|
# Create iFrame
|
|
|
|
$info .= $self->loadTemplate(
|
2019-06-28 13:40:56 +02:00
|
|
|
$req,
|
2017-10-11 13:35:56 +02:00
|
|
|
'samlSpLogout',
|
|
|
|
params => {
|
|
|
|
url => $slo_url,
|
|
|
|
name => $providerName,
|
|
|
|
}
|
|
|
|
);
|
2016-09-22 22:12:56 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
# HTTP-POST
|
|
|
|
elsif ( $method == Lasso::Constants::HTTP_METHOD_POST ) {
|
|
|
|
|
2017-02-15 07:41:50 +01:00
|
|
|
$self->logger->debug("Build POST relay logout request to $providerID");
|
2016-09-22 22:12:56 +02:00
|
|
|
|
|
|
|
my $infos;
|
|
|
|
|
|
|
|
# Store infos
|
|
|
|
$infos->{type} = 'relay';
|
|
|
|
$infos->{_utime} = time;
|
|
|
|
$infos->{url} = $logout->msg_url;
|
|
|
|
$infos->{body} = $logout->msg_body;
|
|
|
|
$infos->{relayState} = $logout->msg_relayState;
|
|
|
|
|
2017-02-20 22:00:05 +01:00
|
|
|
# Create a new relay session
|
|
|
|
my $relayInfos = $self->getSamlSession( undef, $infos );
|
2016-09-22 22:12:56 +02:00
|
|
|
|
|
|
|
my $relayID = $relayInfos->id;
|
|
|
|
|
|
|
|
# Build the URL that could be used to play this logout request
|
2018-06-26 19:13:06 +02:00
|
|
|
my $slo_url =
|
|
|
|
$portal
|
|
|
|
. '/saml/relaySingleLogoutPOST?'
|
|
|
|
. build_urlencoded( relay => $relayID );
|
2016-09-22 22:12:56 +02:00
|
|
|
|
|
|
|
# Create iFrame
|
2017-10-11 13:35:56 +02:00
|
|
|
$info .= $self->loadTemplate(
|
2019-06-28 13:40:56 +02:00
|
|
|
$req,
|
2017-10-11 13:35:56 +02:00
|
|
|
'samlSpLogout',
|
|
|
|
params => {
|
|
|
|
url => $slo_url,
|
|
|
|
name => $providerName,
|
|
|
|
}
|
|
|
|
);
|
2016-09-22 22:12:56 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
# HTTP-SOAP
|
|
|
|
elsif ( $method == Lasso::Constants::HTTP_METHOD_SOAP ) {
|
|
|
|
|
|
|
|
# Build a relay request, to be used after SLO process is done
|
|
|
|
if ($relay) {
|
|
|
|
|
2017-02-15 07:41:50 +01:00
|
|
|
$self->logger->debug(
|
|
|
|
"Build SOAP relay logout request for $providerID");
|
2016-09-22 22:12:56 +02:00
|
|
|
|
|
|
|
my $infos;
|
2018-06-29 06:50:31 +02:00
|
|
|
$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;
|
2016-09-22 22:12:56 +02:00
|
|
|
|
2017-02-20 22:00:05 +01:00
|
|
|
# Create a new relay session
|
|
|
|
my $relayInfos = $self->getSamlSession( undef, $infos );
|
2016-09-22 22:12:56 +02:00
|
|
|
|
|
|
|
my $relayID = $relayInfos->id;
|
|
|
|
|
|
|
|
# Build the URL that could be used to play this logout request
|
|
|
|
my $slo_url =
|
2018-06-26 19:13:06 +02:00
|
|
|
$portal
|
|
|
|
. '/saml/relaySingleLogoutSOAP?'
|
|
|
|
. build_urlencoded( relay => $relayID );
|
2016-09-22 22:12:56 +02:00
|
|
|
|
|
|
|
# Display information to the user
|
2017-10-11 13:35:56 +02:00
|
|
|
$info .= $self->loadTemplate(
|
2019-06-28 13:40:56 +02:00
|
|
|
$req,
|
2017-10-11 13:35:56 +02:00
|
|
|
'samlSpSoapLogout',
|
|
|
|
params => {
|
|
|
|
imgUrl => $slo_url,
|
|
|
|
name => $providerName,
|
|
|
|
}
|
|
|
|
);
|
2016-09-22 22:12:56 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
# Send the request directly
|
|
|
|
else {
|
|
|
|
|
2017-02-15 07:41:50 +01:00
|
|
|
$self->logger->debug("Send SOAP logout request to $providerID");
|
2016-09-22 22:12:56 +02:00
|
|
|
|
|
|
|
my $slo_url = $logout->msg_url;
|
|
|
|
my $slo_body = $logout->msg_body;
|
|
|
|
|
|
|
|
# Send SOAP request and manage response
|
|
|
|
my $sp_response = $self->sendSOAPMessage( $slo_url, $slo_body );
|
|
|
|
|
|
|
|
unless ($sp_response) {
|
2017-02-15 07:41:50 +01:00
|
|
|
$self->logger->error("No logout response to SOAP request");
|
2016-09-22 22:12:56 +02:00
|
|
|
return ( 0, $method, undef );
|
|
|
|
}
|
|
|
|
|
|
|
|
# Process logout response
|
|
|
|
my $sp_result =
|
|
|
|
$self->processLogoutResponseMsg( $logout, $sp_response );
|
|
|
|
|
|
|
|
unless ($sp_result) {
|
2017-02-15 07:41:50 +01:00
|
|
|
$self->logger->error("Fail to process logout response");
|
2016-09-22 22:12:56 +02:00
|
|
|
return ( 0, $method, undef );
|
|
|
|
}
|
|
|
|
|
2020-09-09 12:05:09 +02:00
|
|
|
if ($relayState) {
|
2016-09-22 22:12:56 +02:00
|
|
|
|
2020-09-09 12:05:09 +02:00
|
|
|
# Store success status for this SLO request
|
|
|
|
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(
|
2017-02-15 07:41:50 +01:00
|
|
|
"Unable to store SLO status for $confKey in session $relayState"
|
2020-09-09 12:05:09 +02:00
|
|
|
);
|
|
|
|
}
|
2016-09-22 22:12:56 +02:00
|
|
|
}
|
|
|
|
|
2017-02-15 07:41:50 +01:00
|
|
|
$self->logger->debug("Logout response is valid");
|
2016-09-22 22:12:56 +02:00
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
return ( 1, $method, $info );
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
## @pmethod int sendLogoutRequestToProviders(Lasso::Logout logout, string relayState )
|
|
|
|
# Send logout response issue from a logout request to all other
|
|
|
|
# providers. If information have to be displayed to users, such as
|
|
|
|
# iframe to send HTTP-Redirect or HTTP-POST logout request, then
|
|
|
|
# $self->{_info} will be updated.
|
|
|
|
# @param logout Lasso Logout object
|
|
|
|
# @param relayState Relay State for SLO status
|
|
|
|
# @return int Number of concerned providers.
|
|
|
|
sub sendLogoutRequestToProviders {
|
|
|
|
my ( $self, $req, $logout, $relayState ) = @_;
|
|
|
|
my $server = $self->lassoServer;
|
|
|
|
my $providersCount = 0;
|
2017-10-11 13:35:56 +02:00
|
|
|
my $content = '';
|
2016-09-22 22:12:56 +02:00
|
|
|
|
|
|
|
# Reset providerID into Lasso::Logout object
|
|
|
|
$self->resetProviderIdIndex($logout);
|
|
|
|
|
|
|
|
# Foreach SP found in session, get it from configuration, and send the
|
|
|
|
# appropriate logout request (HTTP,POST,SOAP).
|
|
|
|
while ( my $providerID = $self->getNextProviderId($logout) ) {
|
|
|
|
|
|
|
|
# Send logout request
|
|
|
|
my ( $rstatus, $rmethod, $rinfo ) =
|
|
|
|
$self->sendLogoutRequestToProvider( $req, $logout, $providerID,
|
|
|
|
undef, 1, $relayState );
|
|
|
|
|
|
|
|
next unless ($rstatus);
|
|
|
|
|
|
|
|
# Count providers that have to be request by HTTP redirect
|
|
|
|
$providersCount++;
|
|
|
|
|
|
|
|
# Add information if necessary
|
|
|
|
if ($rinfo) {
|
2017-10-11 13:35:56 +02:00
|
|
|
$content .= $rinfo;
|
2016-09-22 22:12:56 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
# Print some information to the user.
|
2017-10-11 13:35:56 +02:00
|
|
|
$req->info(
|
|
|
|
$self->loadTemplate(
|
2019-06-28 13:40:56 +02:00
|
|
|
$req, 'samlSpsLogout', params => { content => $content }
|
2017-10-11 13:35:56 +02:00
|
|
|
)
|
|
|
|
) if $providersCount;
|
2016-09-22 22:12:56 +02:00
|
|
|
|
|
|
|
return $providersCount;
|
|
|
|
}
|
|
|
|
|
|
|
|
## @method boolean checkSignatureStatus(Lasso::Profile profile)
|
|
|
|
# Check signature status
|
|
|
|
# @param profile Lasso::Profile object
|
|
|
|
# @return result
|
|
|
|
sub checkSignatureStatus {
|
|
|
|
my ( $self, $profile ) = @_;
|
|
|
|
|
|
|
|
eval { Lasso::Profile::get_signature_status($profile); };
|
|
|
|
|
|
|
|
return $self->checkLassoError($@);
|
|
|
|
}
|
|
|
|
|
|
|
|
## @method int authnContext2authnLevel(string authnContext)
|
|
|
|
# Return authentication level corresponding to authnContext
|
|
|
|
# @param authnContext SAML authentication context
|
|
|
|
# return authentication level
|
|
|
|
sub authnContext2authnLevel {
|
|
|
|
my ( $self, $authnContext ) = @_;
|
|
|
|
|
|
|
|
return $self->conf->{samlAuthnContextMapPassword}
|
|
|
|
if ( $authnContext eq $self->getAuthnContext("password") );
|
|
|
|
return $self->conf->{samlAuthnContextMapPasswordProtectedTransport}
|
2017-02-15 07:41:50 +01:00
|
|
|
if (
|
|
|
|
$authnContext eq $self->getAuthnContext("password-protected-transport")
|
|
|
|
);
|
2016-09-22 22:12:56 +02:00
|
|
|
return $self->conf->{samlAuthnContextMapKerberos}
|
|
|
|
if ( $authnContext eq $self->getAuthnContext("kerberos") );
|
|
|
|
return $self->conf->{samlAuthnContextMapTLSClient}
|
|
|
|
if ( $authnContext eq $self->getAuthnContext("tls-client") );
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
## @method int authnLevel2authnContext(int authnLevel)
|
|
|
|
# Return SAML authentication context corresponding to authnLevel
|
|
|
|
# @param authnLevel internal authentication level
|
|
|
|
# return SAML authentication context
|
|
|
|
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} );
|
|
|
|
return $self->getAuthnContext("unspecified");
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
## @method boolean checkDestination(Lasso::Node message, string url)
|
|
|
|
# If SAML Destination attribute is present, check it
|
|
|
|
# @param message SAML request or response
|
|
|
|
# @param url Requested URL
|
|
|
|
# @return Result
|
|
|
|
sub checkDestination {
|
|
|
|
my ( $self, $message, $url ) = @_;
|
|
|
|
my $destination;
|
|
|
|
|
|
|
|
# Read Destination
|
|
|
|
eval { $destination = $message->Destination(); };
|
|
|
|
|
|
|
|
# Ok if no Destination
|
|
|
|
if ( $@ or !$destination ) {
|
2017-02-15 07:41:50 +01:00
|
|
|
$self->logger->debug("No Destination in SAML message");
|
2016-09-22 22:12:56 +02:00
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
2017-02-15 07:41:50 +01:00
|
|
|
$self->logger->debug("Destination $destination found in SAML message");
|
2016-09-22 22:12:56 +02:00
|
|
|
|
|
|
|
# Retrieve full URL
|
|
|
|
my $portal = $self->conf->{portal};
|
|
|
|
$portal =~ s#^(https?://[^/]+)/.*#$1#; # remove path of portal URL
|
|
|
|
$url = $portal . $url;
|
2016-12-08 06:40:50 +01:00
|
|
|
$url =~ s/\?.*//;
|
2016-09-22 22:12:56 +02:00
|
|
|
|
|
|
|
# Compare Destination and URL
|
|
|
|
if ( $destination eq $url ) {
|
2017-02-15 07:41:50 +01:00
|
|
|
$self->logger->debug("Destination match URL $url");
|
2016-09-22 22:12:56 +02:00
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
2017-02-15 07:41:50 +01:00
|
|
|
$self->logger->error("Destination does not match URL $url");
|
2016-09-22 22:12:56 +02:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
## @method hashref getSamlSession(string id)
|
|
|
|
# Try to recover the SAML session corresponding to id and return session
|
|
|
|
# If id is set to undef, return a new session
|
|
|
|
# @param id session reference
|
|
|
|
# @return Lemonldap::NG::Common::Session object
|
|
|
|
sub getSamlSession {
|
2017-02-20 22:00:05 +01:00
|
|
|
my ( $self, $id, $info ) = @_;
|
2016-09-22 22:12:56 +02:00
|
|
|
|
2019-02-07 09:27:56 +01:00
|
|
|
my $samlSession = Lemonldap::NG::Common::Session->new( {
|
2017-09-27 07:00:00 +02:00
|
|
|
storageModule => $self->aModule,
|
|
|
|
storageModuleOptions => $self->amOpts,
|
|
|
|
cacheModule => $self->conf->{localSessionStorage},
|
|
|
|
cacheModuleOptions => $self->conf->{localSessionStorageOptions},
|
|
|
|
id => $id,
|
2018-06-29 06:50:31 +02:00
|
|
|
kind => $self->sessionKind,
|
2017-02-20 22:00:05 +01:00
|
|
|
( $info ? ( info => $info ) : () ),
|
2016-09-22 22:12:56 +02:00
|
|
|
}
|
|
|
|
);
|
|
|
|
|
|
|
|
if ( $samlSession->error ) {
|
|
|
|
if ($id) {
|
2017-02-15 15:16:59 +01:00
|
|
|
$self->userLogger->warn("SAML session $id isn't yet available");
|
2016-09-22 22:12:56 +02:00
|
|
|
}
|
|
|
|
else {
|
2017-02-15 07:41:50 +01:00
|
|
|
$self->logger->error("Unable to create new SAML session");
|
|
|
|
$self->logger->error( $samlSession->error );
|
2016-09-22 22:12:56 +02:00
|
|
|
}
|
|
|
|
return undef;
|
|
|
|
}
|
|
|
|
|
|
|
|
return $samlSession;
|
|
|
|
}
|
|
|
|
|
|
|
|
## @method Lasso::Saml2Attribute createAttribute(string name, string format, string friendly_name)
|
|
|
|
# Create a new SAML attribute
|
|
|
|
# @param name Attribute name
|
|
|
|
# @param format optional Attribute format
|
|
|
|
# @param friendly_name optional Attribute friendly name
|
|
|
|
# @return SAML attribute
|
|
|
|
sub createAttribute {
|
|
|
|
my ( $self, $name, $format, $friendly_name ) = @_;
|
|
|
|
my $attribute;
|
|
|
|
|
|
|
|
# Name is required
|
|
|
|
return unless defined $name;
|
|
|
|
|
|
|
|
# SAML2 attribute
|
|
|
|
eval { $attribute = Lasso::Saml2Attribute->new(); };
|
|
|
|
if ($@) {
|
|
|
|
$self->checkLassoError($@);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
# Default values
|
|
|
|
$friendly_name ||= $name;
|
2019-07-02 20:03:40 +02:00
|
|
|
$format ||= Lasso::Constants::SAML2_ATTRIBUTE_NAME_FORMAT_BASIC;
|
2016-09-22 22:12:56 +02:00
|
|
|
|
|
|
|
# Set attribute properties
|
|
|
|
$attribute->Name($name);
|
|
|
|
$attribute->NameFormat($format);
|
|
|
|
$attribute->FriendlyName($friendly_name);
|
|
|
|
|
|
|
|
return $attribute;
|
|
|
|
}
|
|
|
|
|
|
|
|
## @method Lasso::Saml2AttributeValue createAttributeValue(string value, boolean force_utf8)
|
|
|
|
# Create a new SAML attribute value
|
|
|
|
# @param value Value to store
|
|
|
|
# @param force_utf8 set to 1 to decode UTF8 value
|
|
|
|
# @return SAML attribute value
|
|
|
|
sub createAttributeValue {
|
|
|
|
my ( $self, $value, $force_utf8 ) = @_;
|
|
|
|
my $saml2value;
|
|
|
|
$force_utf8 = 1 unless defined($force_utf8);
|
|
|
|
|
|
|
|
# Value is required
|
|
|
|
return unless defined $value;
|
|
|
|
|
|
|
|
# Decode UTF-8
|
2017-02-15 07:41:50 +01:00
|
|
|
$self->logger->debug("Decode UTF8 value $value") if $force_utf8;
|
2016-09-22 22:12:56 +02:00
|
|
|
$value = decode( "utf8", $value ) if $force_utf8;
|
2017-02-15 07:41:50 +01:00
|
|
|
$self->logger->debug("Create attribute value $value");
|
2016-09-22 22:12:56 +02:00
|
|
|
|
|
|
|
# SAML2 attribute value
|
|
|
|
eval { $saml2value = Lasso::Saml2AttributeValue->new(); };
|
|
|
|
if ($@) {
|
|
|
|
$self->checkLassoError($@);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
my @any;
|
|
|
|
|
|
|
|
# Text node
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
|
|
|
## @method int getEncryptionMode(string encryption_mode)
|
|
|
|
# Return Lasso encryption mode
|
|
|
|
# @param encryption_mode Encryption mode string
|
|
|
|
# @return Lasso encryption mode
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
|
|
|
## @method boolean setProviderEncryptionMode(Lasso::Provider provider, int encryption_mode)
|
|
|
|
# Set encryption mode on a provider
|
|
|
|
# @param provider Lasso::Provider object
|
|
|
|
# @param encryption_mode Lasso encryption mode
|
|
|
|
# @return result
|
|
|
|
sub setProviderEncryptionMode {
|
|
|
|
my ( $self, $provider, $encryption_mode ) = @_;
|
|
|
|
|
|
|
|
eval {
|
|
|
|
Lasso::Provider::set_encryption_mode( $provider, $encryption_mode );
|
|
|
|
};
|
|
|
|
|
|
|
|
return $self->checkLassoError($@);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
## @method boolean deleteSAMLSecondarySessions(string session_id)
|
|
|
|
# Find and delete SAML sessions bounded to a primary session
|
|
|
|
# @param session_id Primary session ID
|
|
|
|
# @return result
|
|
|
|
sub deleteSAMLSecondarySessions {
|
|
|
|
my ( $self, $session_id ) = @_;
|
|
|
|
my $result = 1;
|
|
|
|
|
|
|
|
# Find SAML sessions
|
|
|
|
my $saml_sessions =
|
2017-09-27 07:00:00 +02:00
|
|
|
Lemonldap::NG::Common::Apache::Session->searchOn( $self->amOpts,
|
|
|
|
"_saml_id", $session_id );
|
2016-09-22 22:12:56 +02:00
|
|
|
|
2018-06-29 06:50:31 +02:00
|
|
|
if (
|
|
|
|
my @saml_sessions_keys =
|
|
|
|
grep { $saml_sessions->{$_}->{_session_kind} eq $self->sessionKind }
|
|
|
|
keys %$saml_sessions
|
|
|
|
)
|
|
|
|
{
|
2016-09-22 22:12:56 +02:00
|
|
|
|
|
|
|
foreach my $saml_session (@saml_sessions_keys) {
|
|
|
|
|
|
|
|
# Get session
|
2017-02-15 07:41:50 +01:00
|
|
|
$self->logger->debug("Retrieve SAML session $saml_session");
|
2016-09-22 22:12:56 +02:00
|
|
|
|
|
|
|
my $samlSessionInfo = $self->getSamlSession($saml_session);
|
|
|
|
|
|
|
|
# Delete session
|
|
|
|
if ( $samlSessionInfo->remove ) {
|
2017-02-15 07:41:50 +01:00
|
|
|
$self->logger->debug("SAML session $saml_session deleted");
|
2016-09-22 22:12:56 +02:00
|
|
|
}
|
|
|
|
else {
|
2017-02-15 07:41:50 +01:00
|
|
|
$self->logger->error(
|
|
|
|
"Unable to delete SAML session $saml_session");
|
|
|
|
$self->logger->error( $samlSessionInfo->error );
|
2016-09-22 22:12:56 +02:00
|
|
|
$result = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2018-06-29 14:31:43 +02:00
|
|
|
elsif ($session_id) {
|
|
|
|
$self->logger->debug("No SAML session found for session $session_id");
|
2016-09-22 22:12:56 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return $result;
|
|
|
|
}
|
|
|
|
|
|
|
|
## @method void sendSLOErrorResponse(Lasso::Logout logout, string method)
|
|
|
|
# Send an SLO error response
|
|
|
|
# @param logout Lasso::Logout object
|
|
|
|
# @param method HTTP method
|
|
|
|
sub sendSLOErrorResponse {
|
|
|
|
my ( $self, $req, $logout, $method ) = @_;
|
|
|
|
|
|
|
|
# Load empty session
|
|
|
|
my $session =
|
|
|
|
'<Session xmlns="http://www.entrouvert.org/namespaces/lasso/0.0"/>';
|
|
|
|
|
|
|
|
unless ( $self->setSessionFromDump( $logout, $session ) ) {
|
2016-12-25 16:41:23 +01:00
|
|
|
return $self->p->sendError( $req,
|
|
|
|
"Could not set empty session in logout object", 500 );
|
2016-09-22 22:12:56 +02:00
|
|
|
}
|
|
|
|
|
2020-08-28 21:53:19 +02:00
|
|
|
# Send invalidated SLO response
|
2016-09-22 22:12:56 +02:00
|
|
|
return $self->sendLogoutResponseToServiceProvider( $req, $logout, $method );
|
|
|
|
}
|
|
|
|
|
2016-12-25 16:41:23 +01:00
|
|
|
## @method void sendSLOSoapErrorResponse(Lasso::Logout logout, string method)
|
2016-12-13 20:21:36 +01:00
|
|
|
# Send an SLO error response
|
|
|
|
# @param logout Lasso::Logout object
|
|
|
|
# @param method HTTP method
|
|
|
|
# @return PSGI response
|
|
|
|
sub sendSLOSoapErrorResponse {
|
2016-12-14 06:51:45 +01:00
|
|
|
my ( $self, $req, $logout, $method ) = @_;
|
2016-12-13 20:21:36 +01:00
|
|
|
|
|
|
|
# Load empty session
|
|
|
|
my $session =
|
|
|
|
'<Session xmlns="http://www.entrouvert.org/namespaces/lasso/0.0"/>';
|
|
|
|
|
|
|
|
unless ( $self->setSessionFromDump( $logout, $session ) ) {
|
2016-12-25 16:41:23 +01:00
|
|
|
return $self->p->sendError( $req,
|
|
|
|
"Could not set empty session in logout object" );
|
2016-12-13 20:21:36 +01:00
|
|
|
}
|
|
|
|
my $slo_body = $logout->msg_body;
|
2017-02-15 07:41:50 +01:00
|
|
|
$self->logger->debug("SOAP response $slo_body");
|
2016-12-14 06:51:45 +01:00
|
|
|
return [
|
|
|
|
200,
|
|
|
|
[
|
2020-07-24 14:03:48 +02:00
|
|
|
'Content-Type' => 'text/xml',
|
2016-12-14 06:51:45 +01:00
|
|
|
'Content-Length' => length($slo_body)
|
|
|
|
],
|
|
|
|
[$slo_body]
|
|
|
|
];
|
2016-12-13 20:21:36 +01:00
|
|
|
}
|
|
|
|
|
2016-09-22 22:12:56 +02:00
|
|
|
## @method string getQueryString()
|
|
|
|
# Return query string with or without CGI query_string() method
|
|
|
|
# @return query string
|
|
|
|
sub getQueryString {
|
|
|
|
my ( $self, $req ) = @_;
|
|
|
|
|
|
|
|
my $query_string;
|
|
|
|
|
|
|
|
if ( $self->conf->{samlUseQueryStringSpecific} ) {
|
2017-01-24 06:10:57 +01:00
|
|
|
my @pairs =
|
|
|
|
split( /&/, $req->param('issuerQuery') || $req->query_string );
|
2016-09-22 22:12:56 +02:00
|
|
|
$query_string = join( ';', @pairs );
|
|
|
|
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
# TODO: verify this
|
2017-01-04 17:36:54 +01:00
|
|
|
$query_string = $req->param('issuerQuery') || $req->query_string;
|
2016-09-22 22:12:56 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return $query_string;
|
|
|
|
}
|
|
|
|
|
2016-12-23 17:03:36 +01:00
|
|
|
sub importRealSession {
|
|
|
|
my ( $self, $req, $ssoSession ) = @_;
|
|
|
|
$req->sessionInfo( $ssoSession->data );
|
|
|
|
$req->id( $ssoSession->id );
|
|
|
|
$req->user( $ssoSession->data->{ $self->conf->{whatToTrace} } );
|
|
|
|
}
|
|
|
|
|
2016-12-28 19:38:03 +01:00
|
|
|
sub metadata {
|
|
|
|
my ( $self, $req ) = @_;
|
2019-03-01 10:44:53 +01:00
|
|
|
my $type = $req->param('type') || 'all';
|
2016-12-28 19:38:03 +01:00
|
|
|
require Lemonldap::NG::Common::Conf::SAML::Metadata;
|
|
|
|
if ( my $metadata = Lemonldap::NG::Common::Conf::SAML::Metadata->new() ) {
|
2019-03-07 18:22:16 +01:00
|
|
|
my $s = $metadata->serviceToXML( $self->conf, $type );
|
2016-12-28 19:38:03 +01:00
|
|
|
return [
|
|
|
|
200,
|
|
|
|
[
|
|
|
|
'Content-Type' => 'application/xml',
|
|
|
|
'Content-Length' => length($s),
|
|
|
|
],
|
|
|
|
[$s]
|
|
|
|
];
|
|
|
|
}
|
|
|
|
return $self->p->sendError( $req, 'Unable to build Metadata', 500 );
|
|
|
|
}
|
|
|
|
|
2017-08-29 18:43:43 +02:00
|
|
|
## @method int getSignatureMethod(string signature_method)
|
|
|
|
# Return Lasso signature method
|
|
|
|
# @param signature_method Signature method string
|
|
|
|
# @return Lasso signature method
|
|
|
|
sub getSignatureMethod {
|
|
|
|
my ( $self, $signature_method ) = @_;
|
|
|
|
|
2017-11-17 07:26:44 +01:00
|
|
|
my $signature_method_rsa_sha1 =
|
|
|
|
eval 'Lasso::Constants::SIGNATURE_METHOD_RSA_SHA1';
|
|
|
|
my $signature_method_rsa_sha256 =
|
|
|
|
eval 'Lasso::Constants::SIGNATURE_METHOD_RSA_SHA256';
|
2020-09-22 22:03:13 +02:00
|
|
|
my $signature_method_rsa_sha384 =
|
|
|
|
eval 'Lasso::Constants::SIGNATURE_METHOD_RSA_SHA384';
|
|
|
|
my $signature_method_rsa_sha512 =
|
|
|
|
eval 'Lasso::Constants::SIGNATURE_METHOD_RSA_SHA512';
|
2017-11-17 07:26:44 +01:00
|
|
|
my $signature_method_none = eval 'Lasso::Constants::SIGNATURE_METHOD_NONE';
|
|
|
|
|
|
|
|
return $signature_method_rsa_sha1
|
2018-06-12 18:22:21 +02:00
|
|
|
if ( $signature_method =~ /^RSA_SHA1$/i );
|
2017-11-17 07:26:44 +01:00
|
|
|
return $signature_method_rsa_sha256
|
2018-06-12 18:22:21 +02:00
|
|
|
if ( $signature_method =~ /^RSA_SHA256$/i );
|
2020-09-22 22:03:13 +02:00
|
|
|
return $signature_method_rsa_sha384
|
|
|
|
if ( $signature_method =~ /^RSA_SHA384$/i );
|
|
|
|
return $signature_method_rsa_sha512
|
|
|
|
if ( $signature_method =~ /^RSA_SHA512$/i );
|
2017-11-17 07:26:44 +01:00
|
|
|
return $signature_method_none;
|
2017-08-29 18:43:43 +02:00
|
|
|
}
|
|
|
|
|
2019-09-28 00:18:16 +02:00
|
|
|
## @method boolean setProviderSignatureMethod(Lasso::Provider provider, int signature_method)
|
|
|
|
# Set signature method on a provider
|
|
|
|
# @param provider Lasso::Provider object
|
|
|
|
# @param signature_method Lasso signature method
|
|
|
|
# @return result
|
|
|
|
sub setProviderSignatureMethod {
|
|
|
|
my ( $self, $provider, $signature_method ) = @_;
|
|
|
|
|
|
|
|
eval {
|
|
|
|
my $key = Lasso::Key::new_for_signature_from_file(
|
|
|
|
$self->conf->{samlServicePrivateKeySig},
|
|
|
|
$self->conf->{samlServicePrivateKeySigPwd} || '',
|
|
|
|
$signature_method,
|
|
|
|
$self->conf->{samlServicePublicKeySig}
|
|
|
|
);
|
|
|
|
$provider->set_server_signing_key($key);
|
|
|
|
};
|
|
|
|
|
|
|
|
return $self->checkLassoError($@);
|
|
|
|
}
|
|
|
|
|
2016-09-22 22:12:56 +02:00
|
|
|
1;
|
|
|
|
|
|
|
|
__END__
|
|
|
|
|
|
|
|
=head1 NAME
|
|
|
|
|
|
|
|
=encoding utf8
|
|
|
|
|
2016-12-20 10:39:06 +01:00
|
|
|
Lemonldap::NG::Portal::Lib::SAML - Common SAML functions
|
2016-09-22 22:12:56 +02:00
|
|
|
|
|
|
|
=head1 SYNOPSIS
|
|
|
|
|
2016-12-20 10:39:06 +01:00
|
|
|
use Lemonldap::NG::Portal::Lib::SAML;
|
2016-09-22 22:12:56 +02:00
|
|
|
|
|
|
|
=head1 DESCRIPTION
|
|
|
|
|
|
|
|
This module contains common methods for SAML authentication
|
|
|
|
and user information loading
|
|
|
|
|
|
|
|
=head1 METHODS
|
|
|
|
|
|
|
|
=head2 loadLasso
|
|
|
|
|
|
|
|
Load Lasso module
|
|
|
|
|
|
|
|
=head2 loadService
|
|
|
|
|
|
|
|
Load SAML service by creating a Lasso::Server
|
|
|
|
|
|
|
|
=head2 loadIDPs
|
|
|
|
|
|
|
|
Load SAML identity providers
|
|
|
|
|
|
|
|
=head2 loadSPs
|
|
|
|
|
|
|
|
Load SAML service providers
|
|
|
|
|
|
|
|
=head2 checkMessage
|
|
|
|
|
|
|
|
Check SAML requests and responses
|
|
|
|
|
|
|
|
=head2 checkLassoError
|
|
|
|
|
|
|
|
Log Lasso error code and message if this is actually a Lasso::Error with code > 0
|
|
|
|
|
|
|
|
=head2 createServer
|
|
|
|
|
|
|
|
Load service metadata and create Lasso::Server object
|
|
|
|
|
|
|
|
=head2 addIDP
|
|
|
|
|
|
|
|
Add IDP to an existing Lasso::Server
|
|
|
|
|
|
|
|
=head2 addSP
|
|
|
|
|
|
|
|
Add SP to an existing Lasso::Server
|
|
|
|
|
|
|
|
=head2 addAA
|
|
|
|
|
|
|
|
Add Attribute Authority to an existing Lasso::Server
|
|
|
|
|
|
|
|
=head2 addProvider
|
|
|
|
|
|
|
|
Add provider to an existing Lasso::Server
|
|
|
|
|
|
|
|
=head2 getOrganizationName
|
|
|
|
|
|
|
|
Return name of organization picked up from metadata
|
|
|
|
|
|
|
|
=head2 createAuthnRequest
|
|
|
|
|
|
|
|
Create authentication request for selected IDP
|
|
|
|
|
|
|
|
=head2 createLogin
|
|
|
|
|
|
|
|
Create Lasso::Login object
|
|
|
|
|
|
|
|
=head2 initAuthnRequest
|
|
|
|
|
|
|
|
Init authentication request
|
|
|
|
|
|
|
|
=head2 initIdpInitiatedAuthnRequest
|
|
|
|
|
|
|
|
Init authentication request for IDP initiated
|
|
|
|
|
|
|
|
=head2 buildAuthnRequestMsg
|
|
|
|
|
|
|
|
Build authentication request message
|
|
|
|
|
|
|
|
=head2 processAuthnRequestMsg
|
|
|
|
|
|
|
|
Process authentication request message
|
|
|
|
|
|
|
|
=head2 validateRequestMsg
|
|
|
|
|
|
|
|
Validate request message
|
|
|
|
|
|
|
|
=head2 buildAuthnResponseMsg
|
|
|
|
|
|
|
|
Build authentication response message
|
|
|
|
|
|
|
|
=head2 buildArtifactMsg
|
|
|
|
|
|
|
|
Build artifact message
|
|
|
|
|
|
|
|
=head2 buildAssertion
|
|
|
|
|
|
|
|
Build assertion
|
|
|
|
|
|
|
|
=head2 processAuthnResponseMsg
|
|
|
|
|
|
|
|
Process authentication response message
|
|
|
|
|
|
|
|
=head2 getNameIdentifier
|
|
|
|
|
|
|
|
Get NameID from Lasso Profile
|
|
|
|
|
|
|
|
=head2 createIdentity
|
|
|
|
|
|
|
|
Create Lasso::Identity object
|
|
|
|
|
|
|
|
=head2 createSession
|
|
|
|
|
|
|
|
Create Lasso::Session object
|
|
|
|
|
|
|
|
=head2 acceptSSO
|
|
|
|
|
|
|
|
Accept SSO from IDP
|
|
|
|
|
|
|
|
=head2 storeRelayState
|
|
|
|
|
|
|
|
Store information in relayState database and return
|
|
|
|
|
|
|
|
=head2 extractRelayState
|
|
|
|
|
|
|
|
Extract RelayState information into $self
|
|
|
|
|
|
|
|
=head2 getAssertion
|
|
|
|
|
|
|
|
Get assertion in Lasso::Login object
|
|
|
|
|
|
|
|
=head2 getAttributeValue
|
|
|
|
|
|
|
|
Get SAML attribute value corresponding to name, format and friendly_name
|
|
|
|
Multivaluated values are separated by ';'
|
|
|
|
|
|
|
|
=head2 validateConditions
|
|
|
|
|
|
|
|
Validate conditions
|
|
|
|
|
|
|
|
=head2 createLogoutRequest
|
|
|
|
|
|
|
|
Create logout request for selected entity
|
|
|
|
|
|
|
|
=head2 createLogout
|
|
|
|
|
|
|
|
Create Lasso::Logout object
|
|
|
|
|
|
|
|
=head2 initLogoutRequest
|
|
|
|
|
|
|
|
Init logout request
|
|
|
|
|
|
|
|
=head2 buildLogoutRequestMsg
|
|
|
|
|
|
|
|
Build logout request message
|
|
|
|
|
|
|
|
=head2 setSessionFromDump
|
|
|
|
|
|
|
|
Set session from dump in Lasso::Profile object
|
|
|
|
|
|
|
|
=head2 setIdentityFromDump
|
|
|
|
|
|
|
|
Set identity from dump in Lasso::Profile object
|
|
|
|
|
|
|
|
=head2 getMetaDataURL
|
|
|
|
|
|
|
|
Get URL stored in a service metadata configuration key
|
|
|
|
|
|
|
|
=head2 processLogoutResponseMsg
|
|
|
|
|
|
|
|
Process logout response message
|
|
|
|
|
|
|
|
=head2 processLogoutRequestMsg
|
|
|
|
|
|
|
|
Process logout request message
|
|
|
|
|
|
|
|
=head2 validateLogoutRequest
|
|
|
|
|
|
|
|
Validate logout request
|
|
|
|
|
|
|
|
=head2 buildLogoutResponseMsg
|
|
|
|
|
|
|
|
Build logout response msg
|
|
|
|
|
|
|
|
=head2 storeReplayProtection
|
|
|
|
|
|
|
|
Store ID of an SAML message in Replay Protection base
|
|
|
|
|
|
|
|
=head2 replayProtection
|
|
|
|
|
|
|
|
Check if SAML message do not correspond to a previously responded message
|
|
|
|
|
|
|
|
=head2 resolveArtifact
|
|
|
|
|
|
|
|
Resolve artifact to get the real SAML message
|
|
|
|
|
|
|
|
=head2 storeArtifact
|
|
|
|
|
|
|
|
Store artifact
|
|
|
|
|
|
|
|
=head2 loadArtifact
|
|
|
|
|
|
|
|
Load artifact
|
|
|
|
|
|
|
|
=head2 createArtifactResponse
|
|
|
|
|
|
|
|
Create artifact response
|
|
|
|
|
|
|
|
=head2 processArtRequestMsg
|
|
|
|
|
|
|
|
Process artifact response message
|
|
|
|
|
|
|
|
=head2 processArtResponseMsg
|
|
|
|
|
|
|
|
Process artifact response message
|
|
|
|
|
|
|
|
=head2 sendSOAPMessage
|
|
|
|
|
|
|
|
Send SOAP message and get response
|
|
|
|
|
|
|
|
=head2 createAssertionQuery
|
|
|
|
|
|
|
|
Create a new assertion query
|
|
|
|
|
|
|
|
=head2 createAttributeRequest
|
|
|
|
|
|
|
|
Create an attribute request
|
|
|
|
|
|
|
|
=head2 validateAttributeRequest
|
|
|
|
|
|
|
|
Validate an attribute request
|
|
|
|
|
|
|
|
=head2 processAttributeRequest
|
|
|
|
|
|
|
|
Process an attribute request
|
|
|
|
|
|
|
|
=head2 buildAttributeResponse
|
|
|
|
|
|
|
|
Build attribute response
|
|
|
|
|
|
|
|
=head2 processAttributeResponse
|
|
|
|
|
|
|
|
Process an attribute response
|
|
|
|
|
|
|
|
=head2 getNameIDFormat
|
|
|
|
|
|
|
|
Convert configuration string into SAML2 NameIDFormat string
|
|
|
|
|
|
|
|
=head2 getHttpMethod
|
|
|
|
|
|
|
|
Convert configuration string into Lasso HTTP Method integer
|
|
|
|
|
|
|
|
=head2 getHttpMethodString
|
|
|
|
|
|
|
|
Convert configuration Lasso HTTP Method integer into string
|
|
|
|
|
|
|
|
=head2 getFirstHttpMethod
|
|
|
|
|
|
|
|
Find a suitable HTTP method for an entity with a given protocol
|
|
|
|
|
|
|
|
=head2 disableSignature
|
|
|
|
|
|
|
|
Modify Lasso signature hint to disable signature
|
|
|
|
|
|
|
|
=head2 forceSignature
|
|
|
|
|
|
|
|
Modify Lasso signature hint to force signature
|
|
|
|
|
|
|
|
=head2 disableSignatureVerification
|
|
|
|
|
|
|
|
Modify Lasso signature hint to disable signature verification
|
|
|
|
|
|
|
|
=head2 forceSignatureVerification
|
|
|
|
|
|
|
|
Modify Lasso signature hint to force signature verification
|
|
|
|
|
|
|
|
=head2 getAuthnContext
|
|
|
|
|
|
|
|
Convert configuration string into SAML2 AuthnContextClassRef string
|
|
|
|
|
|
|
|
=head2 timestamp2samldate
|
|
|
|
|
|
|
|
Convert timestamp into SAML2 date format
|
|
|
|
|
|
|
|
=head2 samldate2timestamp
|
|
|
|
|
|
|
|
Convert SAML2 date format into timestamp
|
|
|
|
|
|
|
|
=head2 sendLogoutResponseToServiceProvider
|
|
|
|
|
|
|
|
Send logout response issue from a logout request
|
|
|
|
|
|
|
|
=head2 sendLogoutRequestToProvider
|
|
|
|
|
|
|
|
Send logout request to a provider
|
|
|
|
|
|
|
|
=head2 sendLogoutRequestToProviders
|
|
|
|
|
|
|
|
Send logout response issue from a logout request to all other
|
|
|
|
providers. If information have to be displayed to users, such as
|
2019-04-03 14:15:16 +02:00
|
|
|
iframe to send HTTP-Redirect or HTTP-POST logout request, then
|
|
|
|
$self->{_info} will be updated.
|
2016-09-22 22:12:56 +02:00
|
|
|
|
|
|
|
=head2 checkSignatureStatus
|
|
|
|
|
|
|
|
Check signature status
|
|
|
|
|
|
|
|
=head2 authnContext2authnLevel
|
|
|
|
|
|
|
|
Return authentication level corresponding to authnContext
|
|
|
|
|
|
|
|
=head2 authnLevel2authnContext
|
|
|
|
|
|
|
|
Return SAML authentication context corresponding to authnLevel
|
|
|
|
|
|
|
|
=head2 checkDestination
|
|
|
|
|
|
|
|
If SAML Destination attribute is present, check it
|
|
|
|
|
|
|
|
=head2 getSamlSession
|
|
|
|
|
2018-07-05 22:56:16 +02:00
|
|
|
Try to recover the SAML session corresponding to id and return session data
|
2016-09-22 22:12:56 +02:00
|
|
|
|
|
|
|
=head2 createAttribute
|
|
|
|
|
|
|
|
Create a new SAML attribute
|
|
|
|
|
|
|
|
=head2 createAttributeValue
|
|
|
|
|
|
|
|
Create a new SAML attribute value
|
|
|
|
|
|
|
|
=head2 getEncryptionMode
|
|
|
|
|
|
|
|
Return Lasso encryption mode
|
|
|
|
|
|
|
|
=head2 setProviderEncryptionMode
|
|
|
|
|
|
|
|
Set encryption mode on a provider
|
|
|
|
|
|
|
|
=head2 deleteSAMLSecondarySessions
|
|
|
|
|
|
|
|
Find and delete SAML sessions bounded to a primary session
|
|
|
|
|
|
|
|
=head2 sendSLOErrorResponse
|
|
|
|
|
|
|
|
Send an SLO error response
|
|
|
|
|
|
|
|
=head2 getQueryString
|
|
|
|
|
|
|
|
Get query string with or without CGI query_string() method
|
|
|
|
|
2017-08-29 18:43:43 +02:00
|
|
|
=head2 getSignatureMethod
|
|
|
|
|
|
|
|
Return Lasso signature method
|
|
|
|
|
2019-09-28 00:18:16 +02:00
|
|
|
=head2 setProviderSignatureMethod
|
|
|
|
|
|
|
|
Set signature method on a provider
|
|
|
|
|
2016-09-22 22:12:56 +02:00
|
|
|
=head1 SEE ALSO
|
|
|
|
|
2016-12-20 10:39:06 +01:00
|
|
|
L<Lemonldap::NG::Portal::Auth::SAML>, L<Lemonldap::NG::Portal::UserDBSAML>
|
2016-09-22 22:12:56 +02:00
|
|
|
|
2017-01-04 21:51:46 +01:00
|
|
|
=head1 AUTHORS
|
2016-09-22 22:12:56 +02:00
|
|
|
|
|
|
|
=over
|
|
|
|
|
2017-01-04 21:51:46 +01:00
|
|
|
=item LemonLDAP::NG team L<http://lemonldap-ng.org/team>
|
2016-09-22 22:12:56 +02:00
|
|
|
|
|
|
|
=back
|
|
|
|
|
|
|
|
=head1 BUG REPORT
|
|
|
|
|
|
|
|
Use OW2 system to report bug or ask for features:
|
2017-11-11 14:06:23 +01:00
|
|
|
L<https://gitlab.ow2.org/lemonldap-ng/lemonldap-ng/issues>
|
2016-09-22 22:12:56 +02:00
|
|
|
|
|
|
|
=head1 DOWNLOAD
|
|
|
|
|
|
|
|
Lemonldap::NG is available at
|
|
|
|
L<http://forge.objectweb.org/project/showfiles.php?group_id=274>
|
|
|
|
|
|
|
|
=head1 COPYRIGHT AND LICENSE
|
|
|
|
|
2017-01-04 21:51:46 +01:00
|
|
|
See COPYING file for details.
|
2016-09-22 22:12:56 +02:00
|
|
|
|
|
|
|
This library is free software; you can redistribute it and/or modify
|
|
|
|
it under the terms of the GNU General Public License as published by
|
|
|
|
the Free Software Foundation; either version 2, or (at your option)
|
|
|
|
any later version.
|
|
|
|
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
GNU General Public License for more details.
|
|
|
|
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
|
|
along with this program. If not, see L<http://www.gnu.org/licenses/>.
|
|
|
|
|
|
|
|
=cut
|