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
This commit is contained in:
parent
503fd5d9c3
commit
240c2b56eb
@ -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' };
|
||||
|
||||
unless ( $self->safe->reval($issuerActivationRule) ) {
|
||||
if ( defined $rule ) {
|
||||
$rule =~ s/\$(\w+)/\$self->{sessionInfo}->{$1}/g;
|
||||
|
||||
$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(
|
||||
|
Loading…
Reference in New Issue
Block a user