From 240c2b56eb83b90e55b0d87f264b7c92c16afcce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Oudot?= Date: Mon, 23 Aug 2010 13:27:16 +0000 Subject: [PATCH] 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 --- .../lib/Lemonldap/NG/Manager/Help.pm | 18 +- .../lib/Lemonldap/NG/Manager/_Struct.pm | 29 +-- .../lib/Lemonldap/NG/Manager/_i18n.pm | 8 +- .../lib/Lemonldap/NG/Portal/Simple.pm | 204 ++++++++++++++---- .../t/01-Lemonldap-NG-Portal-Simple.t | 1 + 5 files changed, 200 insertions(+), 60 deletions(-) diff --git a/modules/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Help.pm b/modules/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Help.pm index 20e1e747c..4c277e8b3 100644 --- a/modules/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Help.pm +++ b/modules/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Help.pm @@ -48,11 +48,12 @@ sub help_authParams_en { print <Modules -

LemonLDAP::NG use three types of modules:

+

LemonLDAP::NG use four types of modules:

Authentication module

@@ -94,7 +95,12 @@ sub help_authParams_en { + +

Issuer module

+ EOT @@ -110,6 +116,7 @@ sub help_authParams_fr {
  • auhtentication : comment est effectuée l'authentification,
  • userDB : comment sont collectées les informations de l'utilisateur pour la session,
  • passwordDB : comment est changé le mot de passe.
  • +
  • issuerDB : comment utiliser l'authentification local à travers d'autres protocoles.
  • Module d'authentification

    @@ -151,7 +158,12 @@ sub help_authParams_fr { + +

    Module de fournisseur d'identité

    + EOT diff --git a/modules/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/_Struct.pm b/modules/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/_Struct.pm index b1c84c738..427d33a65 100644 --- a/modules/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/_Struct.pm +++ b/modules/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/_Struct.pm @@ -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', diff --git a/modules/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/_i18n.pm b/modules/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/_i18n.pm index 0622c2d99..fbd97526d 100644 --- a/modules/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/_i18n.pm +++ b/modules/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/_i18n.pm @@ -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', diff --git a/modules/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/Simple.pm b/modules/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/Simple.pm index 006c758c9..3372f9905 100644 --- a/modules/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/Simple.pm +++ b/modules/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/Simple.pm @@ -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(); diff --git a/modules/lemonldap-ng-portal/t/01-Lemonldap-NG-Portal-Simple.t b/modules/lemonldap-ng-portal/t/01-Lemonldap-NG-Portal-Simple.t index 0139792d9..4a9c1357e 100644 --- a/modules/lemonldap-ng-portal/t/01-Lemonldap-NG-Portal-Simple.t +++ b/modules/lemonldap-ng-portal/t/01-Lemonldap-NG-Portal-Simple.t @@ -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(