2009-04-07 22:38:24 +02:00
|
|
|
## @file
|
2009-12-10 18:03:57 +01:00
|
|
|
# SAML Issuer file
|
2009-04-07 22:38:24 +02:00
|
|
|
|
|
|
|
## @class
|
2009-12-10 18:03:57 +01:00
|
|
|
# SAML Issuer class
|
|
|
|
package Lemonldap::NG::Portal::IssuerDBSAML;
|
2009-04-07 22:38:24 +02:00
|
|
|
|
|
|
|
use strict;
|
|
|
|
use Lemonldap::NG::Portal::Simple;
|
2010-02-26 11:53:43 +01:00
|
|
|
use Lemonldap::NG::Portal::_SAML;
|
|
|
|
our @ISA = qw(Lemonldap::NG::Portal::_SAML);
|
2009-04-07 22:38:24 +02:00
|
|
|
|
|
|
|
our $VERSION = '0.01';
|
|
|
|
|
2009-12-10 18:03:57 +01:00
|
|
|
## @method void issuerDBInit()
|
|
|
|
# Load and check SAML configuration
|
|
|
|
# @return Lemonldap::NG::Portal error code
|
|
|
|
sub issuerDBInit {
|
2009-04-07 22:38:24 +02:00
|
|
|
my $self = shift;
|
2010-03-25 12:24:52 +01:00
|
|
|
|
|
|
|
# Load SAML service
|
|
|
|
return PE_ERROR unless $self->loadService();
|
|
|
|
|
|
|
|
# Load SAML identity providers
|
|
|
|
return PE_ERROR unless $self->loadSPs();
|
|
|
|
|
|
|
|
PE_OK;
|
2009-04-07 22:38:24 +02:00
|
|
|
}
|
|
|
|
|
2009-12-10 18:03:57 +01:00
|
|
|
## @apmethod int issuerForUnAuthUser()
|
2010-04-07 14:27:50 +02:00
|
|
|
# Check if there is an SAML authentication request
|
|
|
|
# Called only for unauthenticated users, check isPassive flag
|
2009-04-07 22:38:24 +02:00
|
|
|
# @return Lemonldap::NG::Portal error code
|
2009-12-10 18:03:57 +01:00
|
|
|
sub issuerForUnAuthUser {
|
2010-03-26 17:47:17 +01:00
|
|
|
my $self = shift;
|
2010-03-26 14:56:37 +01:00
|
|
|
my $server = $self->{_lassoServer};
|
|
|
|
|
2010-03-26 17:02:27 +01:00
|
|
|
# Get configuration parameter
|
2010-03-26 14:56:37 +01:00
|
|
|
my $saml_sso_soap_url =
|
|
|
|
$self->getMetaDataURL( "samlIDPSSODescriptorSingleSignOnServiceSOAP", 1 );
|
|
|
|
my $saml_sso_soap_url_ret =
|
|
|
|
$self->getMetaDataURL( "samlIDPSSODescriptorSingleSignOnServiceSOAP", 2 );
|
|
|
|
my $saml_sso_get_url =
|
|
|
|
$self->getMetaDataURL( "samlIDPSSODescriptorSingleSignOnServiceHTTP", 1 );
|
|
|
|
my $saml_sso_get_url_ret =
|
|
|
|
$self->getMetaDataURL( "samlIDPSSODescriptorSingleSignOnServiceHTTP", 2 );
|
|
|
|
|
2010-03-26 17:47:17 +01:00
|
|
|
# Get HTTP request informations to know
|
|
|
|
# if we are receving SAML request or response
|
|
|
|
my $url = $self->url();
|
|
|
|
my $request_method = $self->request_method();
|
|
|
|
my $content_type = $self->content_type();
|
2010-03-26 14:56:37 +01:00
|
|
|
|
2010-03-26 17:47:17 +01:00
|
|
|
if ( $url =~ /^($saml_sso_soap_url|$saml_sso_get_url)$/i ) {
|
2010-03-26 14:56:37 +01:00
|
|
|
|
2010-03-26 17:47:17 +01:00
|
|
|
$self->lmLog( "URL $url detected as an SSO request URL", 'debug' );
|
2010-03-26 14:56:37 +01:00
|
|
|
|
2010-03-26 17:02:27 +01:00
|
|
|
# Check message
|
|
|
|
my ( $request, $response, $method, $relaystate, $artifact ) =
|
2010-03-26 17:47:17 +01:00
|
|
|
$self->checkMessage( $url, $request_method, $content_type );
|
2010-03-26 14:56:37 +01:00
|
|
|
|
2010-03-26 17:02:27 +01:00
|
|
|
# Process the request
|
2010-03-26 17:47:17 +01:00
|
|
|
if ($request) {
|
2010-03-26 14:56:37 +01:00
|
|
|
|
2010-03-26 17:02:27 +01:00
|
|
|
# Create Login object
|
2010-03-26 17:47:17 +01:00
|
|
|
my $login = $self->createLogin($server);
|
2010-03-26 17:02:27 +01:00
|
|
|
|
2010-03-26 14:56:37 +01:00
|
|
|
# Process authentication request
|
|
|
|
my $result;
|
|
|
|
if ($artifact) {
|
|
|
|
$result = $self->processArtRequestMsg( $login, $request );
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
$result = $self->processAuthnRequestMsg( $login, $request );
|
|
|
|
}
|
|
|
|
|
|
|
|
unless ($result) {
|
|
|
|
$self->lmLog( "SSO: Fail to process authentication request",
|
|
|
|
'error' );
|
|
|
|
return PE_ERROR;
|
|
|
|
}
|
|
|
|
|
|
|
|
$self->lmLog( "SSO: authentication request is valid", 'debug' );
|
|
|
|
|
|
|
|
# Get SAML request
|
|
|
|
my $saml_request = $login->request();
|
|
|
|
unless ($saml_request) {
|
|
|
|
$self->lmLog( "No SAML request found", 'error' );
|
|
|
|
return PE_ERROR;
|
|
|
|
}
|
|
|
|
|
|
|
|
# Check isPassive flag
|
|
|
|
my $isPassive = $saml_request->IsPassive();
|
|
|
|
|
|
|
|
if ($isPassive) {
|
2010-04-07 12:11:21 +02:00
|
|
|
$self->lmLog(
|
|
|
|
"Found isPassive flag in SAML request, not compatible with unauthenticated user",
|
|
|
|
'error'
|
|
|
|
);
|
2010-03-26 14:56:37 +01:00
|
|
|
return PE_ERROR;
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2009-04-07 22:38:24 +02:00
|
|
|
PE_OK;
|
|
|
|
}
|
|
|
|
|
2009-12-10 18:03:57 +01:00
|
|
|
## @apmethod int issuerForAuthUser()
|
2009-04-07 22:38:24 +02:00
|
|
|
# Check if there is an SAML authentication request for an authenticated user
|
2010-04-07 14:27:50 +02:00
|
|
|
# Build assertions and redirect user
|
2009-04-07 22:38:24 +02:00
|
|
|
# @return Lemonldap::NG::Portal error code
|
2009-12-10 18:03:57 +01:00
|
|
|
sub issuerForAuthUser {
|
2009-04-08 18:31:13 +02:00
|
|
|
my $self = shift;
|
2010-04-02 18:19:10 +02:00
|
|
|
my $server = $self->{_lassoServer};
|
2010-04-07 14:27:50 +02:00
|
|
|
my $login;
|
2010-04-07 17:14:17 +02:00
|
|
|
my $protocolProfile;
|
|
|
|
my $artifact_method;
|
2010-04-07 18:37:23 +02:00
|
|
|
my $authn_context;
|
2010-04-02 18:19:10 +02:00
|
|
|
|
|
|
|
# Get configuration parameter
|
|
|
|
my $saml_sso_soap_url =
|
|
|
|
$self->getMetaDataURL( "samlIDPSSODescriptorSingleSignOnServiceSOAP", 1 );
|
|
|
|
my $saml_sso_soap_url_ret =
|
|
|
|
$self->getMetaDataURL( "samlIDPSSODescriptorSingleSignOnServiceSOAP", 2 );
|
|
|
|
my $saml_sso_get_url =
|
|
|
|
$self->getMetaDataURL( "samlIDPSSODescriptorSingleSignOnServiceHTTP", 1 );
|
|
|
|
my $saml_sso_get_url_ret =
|
|
|
|
$self->getMetaDataURL( "samlIDPSSODescriptorSingleSignOnServiceHTTP", 2 );
|
|
|
|
|
|
|
|
# Get HTTP request informations to know
|
|
|
|
# if we are receving SAML request or response
|
|
|
|
my $url = $self->url();
|
|
|
|
my $request_method = $self->request_method();
|
|
|
|
my $content_type = $self->content_type();
|
|
|
|
|
|
|
|
if ( $url =~ /^($saml_sso_soap_url|$saml_sso_get_url)$/i ) {
|
|
|
|
|
|
|
|
$self->lmLog( "URL $url detected as an SSO request URL", 'debug' );
|
|
|
|
|
|
|
|
# Check message
|
|
|
|
my ( $request, $response, $method, $relaystate, $artifact ) =
|
|
|
|
$self->checkMessage( $url, $request_method, $content_type );
|
2010-03-26 14:56:37 +01:00
|
|
|
|
2010-04-02 18:19:10 +02:00
|
|
|
# Process the request
|
2010-04-07 12:11:21 +02:00
|
|
|
if ($request) {
|
2010-04-02 18:19:10 +02:00
|
|
|
|
|
|
|
# Create Login object
|
2010-04-07 14:27:50 +02:00
|
|
|
$login = $self->createLogin($server);
|
2010-04-02 18:19:10 +02:00
|
|
|
|
|
|
|
# Process authentication request
|
|
|
|
my $result;
|
|
|
|
if ($artifact) {
|
|
|
|
$result = $self->processArtRequestMsg( $login, $request );
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
$result = $self->processAuthnRequestMsg( $login, $request );
|
|
|
|
}
|
|
|
|
|
|
|
|
unless ($result) {
|
|
|
|
$self->lmLog( "SSO: Fail to process authentication request",
|
|
|
|
'error' );
|
|
|
|
return PE_ERROR;
|
|
|
|
}
|
|
|
|
|
2010-04-07 14:27:50 +02:00
|
|
|
# Validate request
|
|
|
|
unless ( $self->validateRequestMsg( $login, 1, 1 ) ) {
|
|
|
|
$self->lmLog( "Unable to validate SSO request message",
|
|
|
|
'error' );
|
|
|
|
return PE_ERROR;
|
|
|
|
}
|
|
|
|
|
2010-04-02 18:19:10 +02:00
|
|
|
$self->lmLog( "SSO: authentication request is valid", 'debug' );
|
|
|
|
|
2010-04-07 14:27:50 +02:00
|
|
|
# TODO Check AuthnRequest conditions
|
|
|
|
|
|
|
|
# TODO Build NameID
|
|
|
|
|
2010-04-07 18:37:23 +02:00
|
|
|
# Convert authentication method into SAML2 string
|
|
|
|
$authn_context = Lasso::Constants::SAML2_AUTHN_CONTEXT_UNSPECIFIED;
|
|
|
|
|
|
|
|
if ( $self->get_module('auth') =~ /(LDAP|DBI)/i ) {
|
|
|
|
if ( $self->https() ) {
|
|
|
|
$authn_context =
|
|
|
|
$self->getAuthnContext("password-protected-transport");
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
$authn_context = $self->getAuthnContext("password");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( $self->get_module('auth') =~ /(SSL)/i ) {
|
|
|
|
$authn_context = $self->getAuthnContext("x509");
|
|
|
|
}
|
|
|
|
|
|
|
|
$self->lmLog( "Authentication context is $authn_context", 'debug' );
|
|
|
|
|
|
|
|
# Build Assertion
|
|
|
|
# TODO manage NotOnOrAfter date
|
|
|
|
unless ( $self->buildAssertion( $login, $authn_context ) ) {
|
|
|
|
$self->lmLog( "Unable to build assertion", 'error' );
|
|
|
|
return PE_ERROR;
|
|
|
|
}
|
|
|
|
|
|
|
|
$self->lmLog( "SSO: assertion is built", 'debug' );
|
2010-04-07 14:27:50 +02:00
|
|
|
|
2010-04-07 17:14:17 +02:00
|
|
|
# TODO relayState
|
|
|
|
|
2010-04-07 14:27:50 +02:00
|
|
|
# TODO Push mandatory attributes
|
|
|
|
|
2010-04-07 17:14:17 +02:00
|
|
|
# Build SAML response
|
|
|
|
$protocolProfile = $login->protocolProfile();
|
|
|
|
|
|
|
|
# Artifact
|
|
|
|
if ( $protocolProfile ==
|
|
|
|
Lasso::Constants::LOGIN_PROTOCOL_PROFILE_BRWS_ART )
|
|
|
|
{
|
|
|
|
|
|
|
|
# Choose method
|
|
|
|
$artifact_method = $self->getHttpMethod("artifact-get")
|
|
|
|
if ( $method == $self->getHttpMethod("redirect") );
|
|
|
|
$artifact_method = $self->getHttpMethod("artifact-post")
|
|
|
|
if ( $method == $self->getHttpMethod("post") );
|
|
|
|
|
|
|
|
# Build artifact message
|
|
|
|
unless ( $self->buildArtifactMsg( $login, $artifact_method ) ) {
|
|
|
|
$self->lmLog(
|
|
|
|
"Unable to build SSO artifact response message",
|
|
|
|
'error' );
|
|
|
|
return PE_ERROR;
|
|
|
|
}
|
|
|
|
|
|
|
|
$self->lmLog( "SSO: artifact response is built", 'debug' );
|
2010-04-07 14:27:50 +02:00
|
|
|
|
2010-04-07 17:14:17 +02:00
|
|
|
# TODO Get artifact ID and Content, and store them
|
2010-04-07 14:27:50 +02:00
|
|
|
|
|
|
|
}
|
2010-04-02 18:19:10 +02:00
|
|
|
|
2010-04-07 17:14:17 +02:00
|
|
|
# No artifact
|
|
|
|
else {
|
|
|
|
|
|
|
|
unless ( $self->buildAuthnResponseMsg($login) ) {
|
|
|
|
$self->lmLog( "Unable to build SSO response message",
|
|
|
|
'error' );
|
|
|
|
return PE_ERROR;
|
2010-04-02 18:19:10 +02:00
|
|
|
}
|
2010-03-26 14:56:37 +01:00
|
|
|
|
2010-04-07 17:14:17 +02:00
|
|
|
$self->lmLog( "SSO: authentication response is built",
|
|
|
|
'debug' );
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
# Send SSO Response
|
|
|
|
|
|
|
|
# HTTP-REDIRECT
|
|
|
|
if ( $protocolProfile eq
|
|
|
|
Lasso::Constants::LOGIN_PROTOCOL_PROFILE_REDIRECT
|
|
|
|
or $artifact_method == $self->getHttpMethod("artifact-get") )
|
|
|
|
{
|
|
|
|
|
|
|
|
# Redirect user to response URL
|
|
|
|
my $sso_url = $login->msg_url;
|
|
|
|
$self->lmLog( "Redirect user to $sso_url", 'debug' );
|
|
|
|
|
|
|
|
$self->{urldc} = $sso_url;
|
|
|
|
|
|
|
|
$self->_subProcess(qw(autoRedirect));
|
|
|
|
|
|
|
|
# If we are here, there was a problem with GET request
|
|
|
|
$self->lmLog( "SSO response was not sent trough GET", 'error' );
|
|
|
|
return PE_ERROR;
|
|
|
|
}
|
|
|
|
|
|
|
|
# HTTP-POST
|
|
|
|
if ( $protocolProfile eq
|
|
|
|
Lasso::Constants::LOGIN_PROTOCOL_PROFILE_BRWS_POST
|
|
|
|
or $artifact_method == $self->getHttpMethod("artifact-post") )
|
|
|
|
{
|
|
|
|
|
|
|
|
# Use autosubmit form
|
|
|
|
my $sso_url = $login->msg_url;
|
|
|
|
my $sso_body = $login->msg_body;
|
|
|
|
|
|
|
|
$self->{postUrl} = $sso_url;
|
|
|
|
$self->{postFields} = { 'SAMLResponse' => $sso_body };
|
|
|
|
|
|
|
|
# RelayState
|
|
|
|
$self->{postFields}->{'RelayState'} = $login->msg_relayState
|
|
|
|
if ( $login->msg_relayState );
|
|
|
|
|
|
|
|
$self->_subProcess(qw(autoPost));
|
|
|
|
|
|
|
|
# If we are here, there was a problem with POST request
|
|
|
|
$self->lmLog( "SSO response was not sent trough POST",
|
|
|
|
'error' );
|
|
|
|
return PE_ERROR;
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
elsif ($response) {
|
|
|
|
$self->lmLog(
|
|
|
|
"Authentication responses are not managed by this module",
|
|
|
|
'debug' );
|
|
|
|
return PE_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
else {
|
|
|
|
|
|
|
|
# No request or response
|
|
|
|
# This should not happen
|
|
|
|
$self->lmLog( "No request or response found", 'debug' );
|
|
|
|
return PE_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
return PE_OK;
|
2009-04-07 22:38:24 +02:00
|
|
|
}
|
|
|
|
|
2009-12-10 18:03:57 +01:00
|
|
|
## @apmethod int issuerLogout()
|
2009-04-08 18:31:13 +02:00
|
|
|
# TODO
|
2009-12-10 18:03:57 +01:00
|
|
|
# @return Lemonldap::NG::Portal error code
|
|
|
|
sub issuerLogout {
|
2009-04-08 18:31:13 +02:00
|
|
|
my $self = shift;
|
2010-03-26 14:56:37 +01:00
|
|
|
|
|
|
|
print STDERR "IssuerDBSAML: issuerLogout\n";
|
|
|
|
|
2009-12-10 18:03:57 +01:00
|
|
|
PE_OK;
|
2009-04-08 18:31:13 +02:00
|
|
|
}
|
|
|
|
|
2009-04-07 22:38:24 +02:00
|
|
|
1;
|
2009-12-10 18:03:57 +01:00
|
|
|
|
2009-04-07 22:38:24 +02:00
|
|
|
__END__
|
|
|
|
|
|
|
|
=head1 NAME
|
|
|
|
|
2010-01-03 09:09:59 +01:00
|
|
|
=encoding utf8
|
|
|
|
|
2010-04-07 14:27:50 +02:00
|
|
|
Lemonldap::NG::Portal::IssuerDBSAML - SAML IssuerDB for LemonLDAP::NG
|
2009-04-07 22:38:24 +02:00
|
|
|
|
|
|
|
=head1 SYNOPSIS
|
|
|
|
|
2010-04-07 14:27:50 +02:00
|
|
|
use Lemonldap::NG::Portal::SharedConf;
|
|
|
|
|
|
|
|
my $portal = Lemonldap::NG::Portal::SharedConf->new({
|
|
|
|
issuerDB => SAML,
|
|
|
|
});
|
2009-04-07 22:38:24 +02:00
|
|
|
|
|
|
|
=head1 DESCRIPTION
|
|
|
|
|
2010-04-07 14:27:50 +02:00
|
|
|
SAML IssuerDB for LemonLDAP::NG
|
2009-04-07 22:38:24 +02:00
|
|
|
|
|
|
|
=head1 SEE ALSO
|
|
|
|
|
|
|
|
L<Lemonldap::NG::Portal>
|
|
|
|
|
|
|
|
=head1 AUTHOR
|
|
|
|
|
2009-12-13 16:40:33 +01:00
|
|
|
Clément Oudot, E<lt>coudot@linagora.comE<gt>
|
2009-04-07 22:38:24 +02:00
|
|
|
|
|
|
|
=head1 COPYRIGHT AND LICENSE
|
|
|
|
|
2009-12-13 16:40:33 +01:00
|
|
|
Copyright (C) 2009 by Clément Oudot
|
2009-04-07 22:38:24 +02:00
|
|
|
|
|
|
|
This library is free software; you can redistribute it and/or modify
|
|
|
|
it under the same terms as Perl itself, either Perl version 5.10.0 or,
|
|
|
|
at your option, any later version of Perl 5 you may have available.
|
|
|
|
|
|
|
|
=cut
|