lemonldap-ng/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/Lib/SAML.pm

318 lines
9.2 KiB
Perl
Raw Normal View History

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;
use XML::Simple;
use MIME::Base64;
use String::Random;
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
our $VERSION = '2.0.0';
# 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 { {} } );
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";
Glib::Log->set_handler(
"Lasso",
[qw/ error critical warning message info debug /],
sub {
2016-09-21 22:08:50 +02:00
$_[0]
->lmLog( $_[0] . " error " . $_[1] . ": " . $_[2], 'debug' );
2016-09-19 23:00:27 +02:00
}
);
}
# 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) = @_;
# Check for Lasso errors/messages (see BEGIN)
unless (LASSO) {
$self->error("Module Lasso not loaded (see below)");
return 0;
}
if (BADLASSO) {
$self->error('Lasso version >= 2.3.0 required');
return 0;
}
unless (LASSOTHINSESSIONS) {
$self->lmLog( 'Lasso thin-sessions flag could not be set', 'warn' );
}
else {
$self->lmLog( 'Lasso thin-sessions flag set', 'debug' );
}
# Conf initialization
2016-09-21 22:08:50 +02:00
return 0 unless ( $self->lassoServer( $self->loadService ) );
}
2016-09-19 23:00:27 +02:00
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} )
{
$self->error('SAML private and public key not found in configuration');
return 0;
}
my $serviceCertificate;
if ( $self->conf->{samlServiceUseCertificateInResponse}
and $self->conf->{samlServicePublicKeySig} =~ /CERTIFICATE/ )
{
$serviceCertificate = $self->conf->{samlServicePublicKeySig};
$self->lmLog( 'Certificate will be used in SAML responses', 'debug' );
}
# Get metadata from configuration
$self->lmLog( "Get Metadata for this service", 'debug' );
my $service_metadata = Lemonldap::NG::Common::Conf::SAML::Metadata->new();
# Create Lasso server with service metadata
my $server = $self->createServer(
$service_metadata->serviceToXML(
$self->getApacheHtdocsPath() . "/skins/common/saml2-metadata.tpl",
$self
),
$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
);
# Log
unless ($server) {
$self->error('Unable to create Lasso server');
return 0;
}
$self->lmLog( "Service created", 'debug' );
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} } )
{
$self->lmLog( "No IDP found in configuration", 'warn' );
}
# 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( {} );
# TODO: QUESTION: do we have to return 0 (<=> block initialization) if one
# IdP load fails ?
foreach ( keys %{ $self->conf->{samlIDPMetaDataXML} } ) {
$self->lmLog( "Get Metadata for IDP $_", 'debug' );
my $idp_metadata =
$self->conf->{samlIDPMetaDataXML}->{$_}->{samlIDPMetaDataXML};
# Check metadata format
if ( ref $idp_metadata eq "HASH" ) {
$self->error(
"Metadata for IDP $_ is in old format. Please reload them from Manager"
);
return 0;
}
if ( $self->conf->{samlMetadataForceUTF8} ) {
$idp_metadata = encode( "utf8", $idp_metadata );
}
# Add this IDP to Lasso::Server
my $result = $self->addIDP( $self->lassoServer, $idp_metadata );
unless ($result) {
$self->error("Fail to use IDP $_ Metadata");
return 0;
}
# Store IDP entityID and Organization Name
my ($entityID) = ( $idp_metadata =~ /entityID="(.+?)"/i );
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}->{$_}
->{samlIDPMetaDataOptionsEncryptionMode};
my $lasso_encryption_mode = $self->getEncryptionMode($encryption_mode);
unless (
$self->setProviderEncryptionMode(
$self->lassoServer->get_provider($entityID),
$lasso_encryption_mode
)
)
{
$self->error(
"Unable to set encryption mode $encryption_mode on IDP $_");
return 0;
}
$self->lmLog( "Set encryption mode $encryption_mode on IDP $_",
'debug' );
$self->lmLog( "IDP $_ added", 'debug' );
}
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} } )
{
$self->lmLog( "No SP found in configuration", 'warn' );
}
# 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} } ) {
$self->lmLog( "Get Metadata for SP $_", 'debug' );
my $sp_metadata =
$self->conf->{samlSPMetaDataXML}->{$_}->{samlSPMetaDataXML};
# Check metadata format
if ( ref $sp_metadata eq "HASH" ) {
$self->error(
"Metadata for SP $_ is in old format. Please reload them from Manager"
);
return 0;
}
if ( $self->conf->{samlMetadataForceUTF8} ) {
$sp_metadata = encode( "utf8", $sp_metadata );
}
# Add this SP to Lasso::Server
my $result = $self->addSP( $self->lassoServer, $sp_metadata );
unless ($result) {
$self->error("Fail to use SP $_ Metadata");
return 0;
}
# Store SP entityID and Organization Name
my ($entityID) = ( $sp_metadata =~ /entityID="(.+?)"/i );
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}->{$_}
->{samlSPMetaDataOptionsEncryptionMode};
my $lasso_encryption_mode = $self->getEncryptionMode($encryption_mode);
unless (
$self->setProviderEncryptionMode(
$self->lassoServer->get_provider($entityID),
$lasso_encryption_mode
)
)
{
$self->error(
"Unable to set encryption mode $encryption_mode on SP $_");
return 0;
}
$self->lmLog( "Set encryption mode $encryption_mode on SP $_",
'debug' );
$self->lmLog( "SP $_ added", 'debug' );
}
return 1;
}
2016-09-19 23:00:27 +02:00
1;