Commit 240c2b56 authored by Clément OUDOT's avatar Clément OUDOT
Browse files

SAML:

* Use request path to choose IssuerDB module to load
* Store all used IssuerDB module in user session
* Launch issuerLogout method for all used IssuerDB module
* References #102
parent 503fd5d9
......@@ -48,11 +48,12 @@ sub help_authParams_en {
print <<EOT;
<h3>Modules</h3>
<p>LemonLDAP::NG use three types of modules:</p>
<p>LemonLDAP::NG use four types of modules:</p>
<ul>
<li><tt>auhtentication</tt>: how authentication is done,</li>
<li><tt>userDB</tt>: how user information for session are collected,</li>
<li><tt>passwordDB</tt>: how password is changed.</li>
<li><tt>issuerDB</tt>: how use local authentication trough other protocols.</li>
</ul>
<h4>Authentication module</h4>
......@@ -94,7 +95,12 @@ sub help_authParams_en {
<ul>
<li><tt>LDAP</tt>: change password in an LDAP directory,</li>
<li><tt>DBI</tt>: change password in a database,</li>
<li><tt>None</tt>: do nothing,</li>
<li><tt>None</tt>: do nothing.</li>
</ul>
<h4>Issuer module</h4>
<ul>
<li><tt>SAML</tt>: Manage SAML 2.0 SSO, SLO and attribute requests.</li>
</ul>
EOT
......@@ -110,6 +116,7 @@ sub help_authParams_fr {
<li><tt>auhtentication</tt> : comment est effectuée l'authentification,</li>
<li><tt>userDB</tt> : comment sont collectées les informations de l'utilisateur pour la session,</li>
<li><tt>passwordDB</tt> : comment est changé le mot de passe.</li>
<li><tt>issuerDB</tt> : comment utiliser l'authentification local à travers d'autres protocoles.</li>
</ul>
<h4>Module d'authentification</h4>
......@@ -151,7 +158,12 @@ sub help_authParams_fr {
<ul>
<li><tt>LDAP</tt> : changement du mot de passe dans un annuaire LDAP,</li>
<li><tt>DBI</tt> : changement du mot de passe dans une base de données,</li>
<li><tt>None</tt> : pas de changement de mot de passe,</li>
<li><tt>None</tt> : pas de changement de mot de passe.</li>
</ul>
<h4>Module de fournisseur d'identité</h4>
<ul>
<li><tt>SAML</tt> : gère les requêtes SSO, SLO et demande d'attributs SAML 2.0. </li>
</ul>
EOT
......
......@@ -276,8 +276,6 @@ sub struct {
|| $self->defaultConf()->{userDB};
my $pdb = $self->conf->{passwordDB}
|| $self->defaultConf()->{passwordDB};
my $idb = $self->conf->{issurDB}
|| $self->defaultConf()->{issuerDB};
$auth = lc($auth);
$udb = lc($udb);
$pdb = lc($pdb);
......@@ -286,9 +284,8 @@ sub struct {
foreach my $mod (
(
$auth,
( $udb ne ( $auth or $pdb or $idb ) ? $udb : () ),
( $pdb ne ( $auth or $udb or $idb ) ? $pdb : () ),
( $idb ne ( $auth or $udb or $pdb ) ? $idb : () ),
( $udb ne ( $auth or $pdb ) ? $udb : () ),
( $pdb ne ( $auth or $udb ) ? $pdb : () ),
)
)
{
......@@ -323,7 +320,16 @@ sub struct {
authentication => 'text:/authentication:authParams:authParams',
userDB => 'text:/userDB:authParams:userdbParams',
passwordDB => 'text:/passwordDB:authParams:passworddbParams',
issuerDB => 'text:/issuerDB:authParams:issuerdbParams',
# IssuerDB branch
issuerDB => {
_nodes => [qw(issuerDBSAML)],
issuerDBSAML => {
_nodes => [qw(issuerDBSAMLPath issuerDBSAMLRule)],
issuerDBSAMLPath => 'text:/issuerDBSAMLPath',
issuerDBSAMLRule => 'text:/issuerDBSAMLRule',
},
},
# LDAP
ldapParams => {
......@@ -578,12 +584,10 @@ sub struct {
},
security => {
_nodes =>
[qw(userControl portalForceAuthn issuerActivationRule)],
_nodes => [qw(userControl portalForceAuthn)],
userControl => 'text:/userControl:userControl:text',
portalForceAuthn =>
'bool:/portalForceAuthn:portalForceAuthn:bool',
issuerActivationRule => 'textarea:/issuerActivationRule',
},
redirection => {
......@@ -1009,7 +1013,8 @@ sub testStruct {
},
},
https => $boolean,
issuerActivationRule => {
issuerDBSAMLPath => $testNotDefined,
issuerDBSAMLRule => {
test => $perlExpr,
warnTest => sub {
my $e = shift;
......@@ -1017,7 +1022,6 @@ sub testStruct {
1;
},
},
issuerDB => $testNotDefined,
ldapBase => {
test => qr/^(?:\w+=.*|)$/,
msgFail => 'Bad LDAP base',
......@@ -1353,7 +1357,8 @@ sub defaultConf {
domain => 'example.com',
globalStorage => 'Apache::Session::File',
https => '0',
issuerDB => 'Null',
issuerDBSAMLPath => '^/saml/',
issuerDBSAMLRule => '0',
ldapBase => 'dc=example,dc=com',
ldapPort => '389',
ldapPwdEnc => 'utf-8',
......
......@@ -98,8 +98,10 @@ sub en {
groups => 'Groups',
headers => 'HTTP Headers',
https => 'Default value for https parameter',
issuerActivationRule => 'Issuer Activation Rule',
issuerDB => 'Issuer module',
issuerDBSAML => 'SAML',
issuerDBSAMLPath => 'Path',
issuerDBSAMLRule => 'Activation rule',
ldapBase => 'Users search base',
ldapChangePasswordAsUser => 'Change as user',
ldapConnection => 'Connection',
......@@ -384,8 +386,10 @@ sub fr {
groups => 'Groupes',
headers => 'En-têtes HTTP',
https => 'Valeur par défaut du paramètre https',
issuerActivationRule => 'Règle d\'activation du fournisseur',
issuerDB => 'Module fournisseur',
issuerDBSAML => 'SAML',
issuerDBSAMLPath => 'Chemin',
issuerDBSAMLRule => 'Règle d\'activation',
ldapBase => 'Base de recherche des utilisateurs',
ldapChangePasswordAsUser => 'Changement en tant qu\'utilisateur',
ldapConnection => 'Connexion',
......
......@@ -204,8 +204,8 @@ sub new {
or $self->param('logout')
) ? 1 : 0;
# Push authentication/userDB/passwordDb/issuerDB modules in @ISA
foreach my $type (qw(authentication userDB passwordDB issuerDB)) {
# Push authentication/userDB/passwordDB modules in @ISA
foreach my $type (qw(authentication userDB passwordDB)) {
my $module_name = 'Lemonldap::NG::Portal::';
my $db_type = $type;
my $db_name = $self->{$db_type};
......@@ -214,7 +214,6 @@ sub new {
$db_type =~ s/authentication/Auth/;
$db_type =~ s/userDB/UserDB/;
$db_type =~ s/passwordDB/PasswordDB/;
$db_type =~ s/issuerDB/IssuerDB/;
# Full module name
$module_name .= $db_type . $db_name;
......@@ -235,6 +234,71 @@ sub new {
}
}
# Check issuerDB path to load the correct issuerDB module
foreach my $issuerDBtype (qw(SAML OpenID)) {
my $module_name = 'Lemonldap::NG::Portal::IssuerDB' . $issuerDBtype;
$self->lmLog( "[IssuerDB activation] Try issuerDB module $issuerDBtype",
'debug' );
# Check the path
my $path = $self->{ "issuerDB" . $issuerDBtype . "Path" };
if ( defined $path ) {
$self->lmLog( "[IssuerDB activation] Found path $path", 'debug' );
# Get current path
my $url_path = $self->url( -absolute => 1 );
$self->lmLog(
"[IssuerDB activation] Path of current request is $url_path",
'debug' );
# Match regular expression
if ( $url_path =~ m#$path# ) {
$self->abort( "Configuration error",
"Unable to load $module_name" )
unless $self->loadModule($module_name);
# Remember loaded module
$self->{_activeIssuerDB} = $issuerDBtype;
$self->lmLog(
"[IssuerDB activation] IssuerDB module $issuerDBtype loaded",
'debug'
);
last;
}
else {
$self->lmLog(
"[IssuerDB activation] Path do not match, trying next",
'debug' );
next;
}
}
else {
$self->lmLog( "[IssuerDB activation] No path defined", 'debug' );
next;
}
}
# Load default issuerDB module if none was choosed
unless ( $self->{_activeIssuerDB} ) {
# Manage old configuration format
my $db_type = $self->{'issuerDB'} || 'Null';
my $module_name = 'Lemonldap::NG::Portal::IssuerDB' . $db_type;
$self->abort( "Configuration error", "Unable to load $module_name" )
unless $self->loadModule($module_name);
# Remember loaded module
$self->{_activeIssuerDB} = $db_type;
$self->lmLog( "[IssuerDB activation] IssuerDB module $db_type loaded",
'debug' );
}
# Notifications
if ( $self->{notification} ) {
require Lemonldap::NG::Portal::Notification;
......@@ -364,7 +428,6 @@ sub setDefaultValues {
"[LemonLDAP::NG] Password reset confirmation";
$self->{mailSessionKey} ||= 'mail';
$self->{mailUrl} ||= $self->{portal} . "/mail.pl";
$self->{issuerDB} ||= 'Null';
$self->{multiValuesSeparator} ||= '; ';
$self->{activeTimer} = 1 unless ( defined( $self->{activeTimer} ) );
$self->{infoFormMethod} ||= "get";
......@@ -658,6 +721,45 @@ sub updateSession {
}
## @method void addSessionValue(string key, string value, string id)
# Add a value into session key if not already present
# @param key Session key
# @param value Value to add
# @param id optional Session identifier
sub addSessionValue {
my ( $self, $key, $value, $id ) = splice @_;
# Mandatory parameters
return unless defined $key;
return unless defined $value;
# Get current key value
my $old_value = $self->{sessionInfo}->{$key};
# Split old values
if ( defined $old_value ) {
my @old_values = split /\Q$self->{multiValuesSeparator}\E/, $old_value;
# Do nothing if value already exists
foreach (@old_values) {
return if ( $_ eq $value );
}
# Add separator
$old_value .= $self->{multiValuesSeparator};
}
else {
$old_value = "";
}
# Store new value
my $new_value = $old_value . $value;
$self->updateSession( { $key => $new_value }, $id );
# Return
return;
}
## @method string getFirstValue(string value)
# Get the first value of a multivaluated session value
# @param value the complete value
......@@ -758,7 +860,7 @@ sub get_module {
}
if ( $type =~ /issuer/i ) {
return $self->{issuerDB};
return $self->{_activeIssuerDB};
}
return;
......@@ -834,7 +936,7 @@ sub _deleteSession {
}
}
my $logged_user = $h->{ $self->{whatToTrace} };
my $logged_user = $h->{ $self->{whatToTrace} } || 'unknown';
# Try to purge local cache
# (if an handler is running on the same server)
......@@ -1075,15 +1177,32 @@ sub controlExistingSession {
return PE_ERROR;
}
# Call issuerDB logout
eval {
# Call issuerDB logout on each used issuerDBmodule
my $issuerDBList = $self->{sessionInfo}->{_issuerDB};
if ( defined $issuerDBList ) {
foreach my $issuerDBtype (
split( /\Q$self->{multiValuesSeparator}\E/, $issuerDBList )
)
{
my $module_name =
'Lemonldap::NG::Portal::IssuerDB' . $issuerDBtype;
$self->lmLog(
"Process logout for issuerDB module $issuerDBtype",
'debug' );
# Load current IssuerDB module
unless ( $self->loadModule($module_name) ) {
$self->lmLog( "Unable to load $module_name", 'error' );
next;
}
$self->{error} =
$self->_subProcess(qw(issuerDBInit authInit issuerLogout));
};
if ($@) {
$self->lmLog( "Error when calling issuerLogout: $@", 'debug' );
$self->_subProcess( $module_name . "::issuerDBInit",
'authInit', $module_name . '::issuerLogout' );
}
}
return $self->{error} if $self->{error} > 0;
# Call authentication logout
eval { $self->{error} = $self->_sub('authLogout'); };
......@@ -1188,18 +1307,7 @@ sub existingSession {
PE_DONE;
}
## @apmethod int issuerDBInit()
# Set _issuerDB
# call issuerDBInit in issuerDB* module
# @return Lemonldap::NG::Portal constant
sub issuerDBInit {
my $self = shift;
# Get the current issuer module
$self->{sessionInfo}->{_issuerDB} = $self->get_module("issuer");
return $self->SUPER::issuerDBInit();
}
# issuerDBInit(): must be implemented in IssuerDB* module
# authInit(): must be implemented in Auth* module
......@@ -1577,38 +1685,48 @@ sub checkNotification {
}
## @apmethod int issuerForAuthUser()
# Check IssuerDB activation rule and then call IssuerDB module method
# Check IssuerDB activation rule
# Register used module in user session
# @return Lemonldap::NG::Portal constant
sub issuerForAuthUser {
my $self = shift;
# If no rule defined, it's ok
return $self->SUPER::issuerForAuthUser()
unless defined $self->{issuerActivationRule};
# User information
my $logged_user = $self->{sessionInfo}->{ $self->{whatToTrace} }
|| 'unknown';
# Check activation rule
my $issuerActivationRule = $self->{issuerActivationRule};
$issuerActivationRule =~ s/\$(\w+)/\$self->{sessionInfo}->{$1}/g;
# Get active module
my $issuerDBtype = $self->get_module('issuer');
$self->lmLog( "Applying issuerActivationRule: $issuerActivationRule",
'debug' );
# Eval activation rule
my $rule = $self->{ 'issuerDB' . $issuerDBtype . 'Rule' };
if ( defined $rule ) {
$rule =~ s/\$(\w+)/\$self->{sessionInfo}->{$1}/g;
unless ( $self->safe->reval($issuerActivationRule) ) {
$self->lmLog( "Applying rule: $rule", 'debug' );
unless ( $self->safe->reval($rule) ) {
$self->lmLog(
"User "
. $self->{sessionInfo}->{_user}
. " was not allowed to use IssuerDB module",
"User $logged_user"
. " was not allowed to use IssuerDB $issuerDBtype",
'warn'
);
return PE_OK;
}
}
else {
$self->lmLog( "No rule found for IssuerDB $issuerDBtype", 'debug' );
}
$self->lmLog(
"User "
. $self->{sessionInfo}->{_user}
. " allowed to use IssuerDB module",
'debug'
);
"User $logged_user" . " allowed to use IssuerDB $issuerDBtype",
'debug' );
# Register IssuerDB module in session
$self->addSessionValue( '_issuerDB', $issuerDBtype, $self->{id} );
# Call IssuerDB module method
return $self->SUPER::issuerForAuthUser();
......
......@@ -31,6 +31,7 @@ $ENV{SCRIPT_FILENAME} = '/tmp/test.pl';
$ENV{REQUEST_METHOD} = 'GET';
$ENV{REQUEST_URI} = '/';
$ENV{QUERY_STRING} = '';
$ENV{REMOTE_ADDR} = '127.0.0.1';
ok(
$p = Lemonldap::NG::Portal::Simple->new(
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment