|
|
|
@ -4,6 +4,9 @@ use strict; |
|
|
|
|
use Mouse; |
|
|
|
|
use Lemonldap::NG::Portal::Main::Constants qw( |
|
|
|
|
PE_OK |
|
|
|
|
PE_SAML_ATTR_ERROR |
|
|
|
|
PE_SAML_LOAD_IDP_ERROR |
|
|
|
|
PE_SAML_LOAD_SERVICE_ERROR |
|
|
|
|
); |
|
|
|
|
|
|
|
|
|
use Lemonldap::NG::Common::Conf::SAML::Metadata; |
|
|
|
@ -17,8 +20,170 @@ extends 'Lemonldap::NG::Common::Module', 'Lemonldap::NG::Portal::Lib::SAML'; |
|
|
|
|
sub init { |
|
|
|
|
my ($self) = @_; |
|
|
|
|
|
|
|
|
|
unless ( $self->p->getModule( undef, 'auth' ) =~ /^SAML/ ) { |
|
|
|
|
$self->error( |
|
|
|
|
"SAML user module requires SAML authentication" |
|
|
|
|
); |
|
|
|
|
return 0; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
# Load SAML service and SAML SP list |
|
|
|
|
return ( $self->SUPER::init and $self->loadSPs ); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
# RUNNONG METHODS |
|
|
|
|
|
|
|
|
|
# Does nothing |
|
|
|
|
sub getUser { |
|
|
|
|
PE_OK; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
# Get all required attributes |
|
|
|
|
sub setSessionInfo { |
|
|
|
|
my ( $self, $req ) = @_; |
|
|
|
|
my $idp = $req->datas->{_idp}; |
|
|
|
|
my $idpConfKey = $req->datas->{_idpConfKey}; |
|
|
|
|
my $nameid = $req->datas->{_nameID}; |
|
|
|
|
|
|
|
|
|
my $exportedAttr; |
|
|
|
|
|
|
|
|
|
# Force UTF-8 |
|
|
|
|
my $force_utf8 = |
|
|
|
|
$self->conf->{samlIDPMetaDataOptions}->{$idpConfKey} |
|
|
|
|
->{samlIDPMetaDataOptionsForceUTF8}; |
|
|
|
|
|
|
|
|
|
# Get all required attributes, not already set |
|
|
|
|
# in setAuthSessionInfo() |
|
|
|
|
foreach ( |
|
|
|
|
keys %{ $self->conf->{samlIDPMetaDataExportedAttributes}->{$idpConfKey} } ) |
|
|
|
|
{ |
|
|
|
|
|
|
|
|
|
# Extract fields from exportedAttr value |
|
|
|
|
my ( $mandatory, $name, $format, $friendly_name ) = |
|
|
|
|
split( /;/, |
|
|
|
|
$self->conf->{samlIDPMetaDataExportedAttributes}->{$idpConfKey}->{$_} ); |
|
|
|
|
|
|
|
|
|
# Keep mandatory attributes not sent in authentication response |
|
|
|
|
if ( $mandatory and not defined $req->{sessionInfo}->{$_} ) { |
|
|
|
|
$exportedAttr->{$_} = |
|
|
|
|
$self->conf->{samlIDPMetaDataExportedAttributes}->{$idpConfKey}->{$_}; |
|
|
|
|
$self->lmLog( "Attribute $_ will be requested to $idpConfKey", |
|
|
|
|
'debug' ); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
unless ( keys %$exportedAttr ) { |
|
|
|
|
$self->lmLog( |
|
|
|
|
"All mandatory attributes were present in authentication response", |
|
|
|
|
'debug' |
|
|
|
|
); |
|
|
|
|
return PE_OK; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
# Save current Lasso::Server object, and get a new one |
|
|
|
|
my $current_server = $self->lassoServer; |
|
|
|
|
$self->loadService(1); |
|
|
|
|
my $server = $self->lassoServer; |
|
|
|
|
|
|
|
|
|
unless ($server) { |
|
|
|
|
$self->lmLog( "Unable to create service for attribute request", |
|
|
|
|
'error' ); |
|
|
|
|
return PE_SAML_LOAD_SERVICE_ERROR; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
$self->lmLog( "Service for attribute request created", 'debug' ); |
|
|
|
|
|
|
|
|
|
# Add current IDP as Attribute Authority |
|
|
|
|
my $idp_metadata = |
|
|
|
|
$self->conf->{samlIDPMetaDataXML}->{$idpConfKey}->{samlIDPMetaDataXML}; |
|
|
|
|
|
|
|
|
|
if ( $self->conf->{samlMetadataForceUTF8} ) { |
|
|
|
|
$idp_metadata = encode( "utf8", $idp_metadata ); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
# Add this IDP to Lasso::Server as AA |
|
|
|
|
unless ( $self->addAA( $server, $idp_metadata ) ) { |
|
|
|
|
$self->lmLog( |
|
|
|
|
"Fail to use IDP $idpConfKey Metadata as Attribute Authority", |
|
|
|
|
'error' ); |
|
|
|
|
return PE_SAML_LOAD_IDP_ERROR; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
# Build Attribute Request |
|
|
|
|
my $query = |
|
|
|
|
$self->createAttributeRequest( $server, $idp, $exportedAttr, $nameid ); |
|
|
|
|
|
|
|
|
|
unless ($query) { |
|
|
|
|
$self->lmLog( "Unable to build attribute request for $idpConfKey", |
|
|
|
|
'error' ); |
|
|
|
|
return PE_SAML_ATTR_ERROR; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
# Use SOAP to send request and get response |
|
|
|
|
my $query_url = $query->msg_url; |
|
|
|
|
my $query_body = $query->msg_body; |
|
|
|
|
|
|
|
|
|
# Send SOAP request and manage response |
|
|
|
|
my $response = $self->sendSOAPMessage( $query_url, $query_body ); |
|
|
|
|
|
|
|
|
|
unless ($response) { |
|
|
|
|
$self->lmLog( "No attribute response to SOAP request", 'error' ); |
|
|
|
|
return PE_SAML_ATTR_ERROR; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
# Manage Attribute Response |
|
|
|
|
my $result = $self->processAttributeResponse( $server, $response ); |
|
|
|
|
|
|
|
|
|
unless ($result) { |
|
|
|
|
$self->lmLog( "Fail to process attribute response", 'error' ); |
|
|
|
|
return PE_SAML_ATTR_ERROR; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
# Attributes in response |
|
|
|
|
my @response_attributes; |
|
|
|
|
eval { |
|
|
|
|
@response_attributes = |
|
|
|
|
$result->response()->Assertion()->AttributeStatement()->Attribute(); |
|
|
|
|
}; |
|
|
|
|
if ($@) { |
|
|
|
|
$self->lmLog( "No attributes defined in attribute response", 'error' ); |
|
|
|
|
return PE_SAML_ATTR_ERROR; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
# Check we have all required attributes |
|
|
|
|
foreach ( keys %$exportedAttr ) { |
|
|
|
|
|
|
|
|
|
# Extract fields from exportedAttr value |
|
|
|
|
my ( $mandatory, $name, $format, $friendly_name ) = |
|
|
|
|
split( /;/, $exportedAttr->{$_} ); |
|
|
|
|
|
|
|
|
|
# Try to get value |
|
|
|
|
my $value = $self->getAttributeValue( $name, $format, $friendly_name, |
|
|
|
|
\@response_attributes, $force_utf8 ); |
|
|
|
|
|
|
|
|
|
unless ($value) { |
|
|
|
|
$self->lmLog( |
|
|
|
|
"Attribute $_ is mandatory, but was not delivered by $idpConfKey", |
|
|
|
|
'error' |
|
|
|
|
); |
|
|
|
|
return PE_SAML_ATTR_ERROR; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
$self->lmLog( "Get value $value for attribute $_", 'debug' ); |
|
|
|
|
|
|
|
|
|
# Store value in sessionInfo |
|
|
|
|
$req->{sessionInfo}->{$_} = $value; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
# Restore current Lasso::Server |
|
|
|
|
$self->lassoServer = $current_server; |
|
|
|
|
|
|
|
|
|
PE_OK; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
# Does nothing |
|
|
|
|
sub setGroups { |
|
|
|
|
PE_OK; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
1; |
|
|
|
|