Commit 80f199fb authored by Yadd's avatar Yadd
Browse files

LEMONLDAP::NG : LDAP User database module

parent 02fb3d96
Changes
example/apps/apps-list.dtd
example/apps/apps-list.xml
example/apps/docs.png
example/apps/tools.png
example/apps/web.png
example/apps/wheels.png
example/AuthLA/idps.xml
example/AuthLA/index.pl
example/AuthLA/liberty/assertionConsumer.pl
......@@ -33,6 +39,7 @@ example/AuthLA/tpl/themes/federid/msg-std.png
example/AuthLA/tpl/themes/federid/page-bg.png
example/AuthLA/tpl/themes/federid/sso.css
example/AuthLA/tpl/themes/federid/wui.css
example/error.pl
example/index.pl
example/index_simple.pl
example/index_skin.pl
......@@ -40,19 +47,43 @@ example/PortalStatus.pl
example/scripts/purgeCentralCache
example/scripts/purgeCentralCache.cron.d
example/skins/default/hatch.gif
example/skins/default/login.tpl
example/skins/default/logo_lemonldap-ng.png
example/skins/default/msg-std.png
example/skins/default/styles.css
example/skins/pastel/error.png
example/skins/pastel/error.tpl
example/skins/pastel/favicon.ico
example/skins/pastel/footer.tpl
example/skins/pastel/hatch.gif
example/skins/pastel/header.tpl
example/skins/pastel/jquery-tabs.js
example/skins/pastel/jquery.js
example/skins/pastel/lock.png
example/skins/pastel/login.tpl
example/skins/pastel/menu.tpl
example/skins/pastel/ok.png
example/skins/pastel/stop.png
example/skins/pastel/styles.css
example/skins/pastel/valid-xhtml10-blue.png
example/skins/pastel/vcss-blue.gif
example/skins/pastel/warning.png
example/skins/pastel/warning_triangle.png
example/slavePortal.pl
lib/Lemonldap/NG/Portal.pm
lib/Lemonldap/NG/Portal/_i18n.pm
lib/Lemonldap/NG/Portal/_LDAP.pm
lib/Lemonldap/NG/Portal/AuthApache.pm
lib/Lemonldap/NG/Portal/AuthCAS.pm
lib/Lemonldap/NG/Portal/AuthLA.pm
lib/Lemonldap/NG/Portal/AuthLDAP.pm
lib/Lemonldap/NG/Portal/AuthSSL.pm
lib/Lemonldap/NG/Portal/CDA.pm
lib/Lemonldap/NG/Portal/Error.pm
lib/Lemonldap/NG/Portal/Menu.pm
lib/Lemonldap/NG/Portal/SharedConf.pm
lib/Lemonldap/NG/Portal/Simple.pm
lib/Lemonldap/NG/Portal/UserDBLDAP.pm
Makefile.PL
MANIFEST This list of files
META.yml
......
......@@ -6,6 +6,7 @@ use Lemonldap::NG::Portal::Simple;
our $VERSION = '0.1';
sub authInit {
1;
}
# By default, authentication is valid if REMOTE_USER environment
......
......@@ -4,7 +4,14 @@ use Lemonldap::NG::Portal::Simple;
our $VERSION = '0.1';
sub ldap {
my $self = shift;
return $self->{ldap} || Lemonldap::NG::Portal::_LDAP->new({portal=>$self});
}
sub authInit {
my $self = shift;
return $self->ldap;
}
sub extractFormInfo {
......@@ -23,9 +30,6 @@ sub setAuthSessionInfo {
sub authenticate {
my $self = shift;
$self->unbind();
my $err;
return $err unless ( ( $err = $self->connectLDAP ) == PE_OK );
# Check if we use Ppolicy control
if ( $self->{ldapPpolicyControl} ) {
......@@ -41,7 +45,7 @@ sub authenticate {
my $pp = Net::LDAP::Control::PasswordPolicy->new;
# Bind with user credentials
my $mesg = $self->{ldap}->bind(
my $mesg = $self->ldap->{ldap}->bind(
$self->{dn},
password => $self->{password},
control => [$pp]
......@@ -83,7 +87,7 @@ sub authenticate {
else {
return PE_BADCREDENTIALS
unless (
$self->_bind( $self->{ldap}, $self->{dn}, $self->{password} ) );
$self->ldap->_bind( $self->{dn}, $self->{password} ) );
}
$self->{sessionInfo}->{authenticationLevel} = 2;
PE_OK;
......
......@@ -5,7 +5,6 @@ use warnings;
use Exporter 'import';
use Net::LDAP;
use warnings;
use MIME::Base64;
use CGI;
......@@ -75,29 +74,29 @@ sub new {
die( "Module " . $self->{globalStorage} . " not found in \@INC" ) if ($@);
die("You've to indicate a domain for cookies") unless ( $self->{domain} );
$self->{domain} =~ s/^([^\.])/.$1/;
$self->{ldapServer} ||= 'localhost';
$self->{ldapPort} ||= 389;
$self->{securedCookie} ||= 0;
$self->{cookieName} ||= "lemonldap";
$self->{ldapPpolicyControl} ||= 0;
$self->{authentication} ||= 'LDAP';
$self->{userDB} ||= 'LDAP';
$self->{authentication} =~ s/^ldap/LDAP/;
# Authentication module is required and has to be in @ISA
my $tmp = 'Lemonldap::NG::Portal::Auth' . $self->{authentication};
$tmp =~ s/\s.*$//;
eval "require $tmp";
die($@) if ($@);
push @ISA, $tmp;
# $self->{authentication} can contains arguments (key1 = scalar_value;
# key2 = ...)
$tmp = $self->{authentication};
$tmp =~ s/^\w+\s*//;
my %h = split( /\s*[=;]\s*/, $tmp ) if ($tmp);
%$self = ( %h, %$self );
$self->authInit();
foreach(qw(authentication userDB)) {
my $tmp = 'Lemonldap::NG::Portal::' . ($_ eq 'userDB'?'UserDB':'Auth') . $self->{$_};
$tmp =~ s/\s.*$//;
eval "require $tmp";
die($@) if ($@);
push @ISA, $tmp;
# $self->{authentication} and $self->{userDB} can contains arguments
# (key1 = scalar_value; key2 = ...)
$tmp = $self->{$_};
$tmp =~ s/^\w+\s*//;
my %h = split( /\s*[=;]\s*/, $tmp ) if ($tmp);
%$self = ( %h, %$self );
}
$self->authInit() or die("$class : unable to load initialize authentication module");
$self->userDBInit() or die("$class : unable to load initialize user database module");
return $self;
}
......@@ -169,23 +168,6 @@ sub translate_template {
}
}
# Private sub used to bind to LDAP server both with Lemonldap::NG account and user
# credentials if LDAP authentication is used
sub _bind {
my ( $self, $ldap, $dn, $password ) = @_;
my $mesg;
if ( $dn and $password ) { # named bind
$mesg = $ldap->bind( $dn, password => $password );
}
else { # anonymous bind
$mesg = $ldap->bind();
}
if ( $mesg->code() != 0 ) {
return 0;
}
return 1;
}
# CGI.pm overload to add Lemonldap::NG cookie
sub header {
my $self = shift;
......@@ -208,6 +190,40 @@ sub redirect {
}
}
# getSessionInfo
# Read session and store it in $self
sub getSessionInfo {
my $self = shift;
my %cookies = fetch CGI::Cookie;
# Test if Lemonldap::NG cookie is available
if ( $cookies{ $self->{cookieName} }
and my $id = $cookies{ $self->{cookieName} }->value )
{
my %h;
# Trying to recover session from global session storage
eval {
tie %h, $self->{globalStorage}, $id, $self->{globalStorageOptions};
};
if ( $@ or not tied(%h) ) {
# Session not available (expired ?)
print STDERR
"Session $id isn't yet available ($ENV{REMOTE_ADDR})\n";
return undef;
}
# Store session values
foreach ( keys %h ) {
$self->{sessionInfo}->{$_} = $h{$_};
}
untie %h;
}
}
# Externalise functions execution
sub _subProcess {
my $self = shift;
......@@ -240,17 +256,19 @@ sub updateStatus {
# different than PE_OK #
###############################################################
# extractFormInfo, setAuthSessionInfo and authenticate must be implemented in
# auth modules
# Process call functions issued from :
# * itself : controlUrlOrigin, controlExistingSession, setMacros, setGroups,
# store, buildCookie, log, autoredirect
# * authentication module : extractFormInfo, setAuthSessionInfo, authenticate
# * user database module : getUser, setSessionInfo
sub process {
my ($self) = @_;
$self->{error} = PE_OK;
$self->{error} = $self->_subProcess(
qw(controlUrlOrigin controlExistingSession extractFormInfo formateParams
formateFilter connectLDAP bind search setAuthSessionInfo
setSessionInfo setMacros setGroups authenticate store unbind
buildCookie log autoRedirect)
qw(controlUrlOrigin controlExistingSession extractFormInfo getUser
setAuthSessionInfo setSessionInfo setMacros setGroups authenticate
store buildCookie log autoRedirect)
);
$self->updateStatus;
return ( ( $self->{error} > 0 ) ? 0 : 1 );
......@@ -343,150 +361,6 @@ sub existingSession {
# Unused. You can overload if you have to modify user and password before
# authentication
sub formateParams() {
PE_OK;
}
# 4. By default, the user is searched in the LDAP server with its UID. To use
# it with Active Directory, overload it to use CN instead of UID.
sub formateFilter {
my $self = shift;
$self->{filter} = $self->{authFilter}
|| "(&(uid=" . $self->{user} . ")(objectClass=inetOrgPerson))";
PE_OK;
}
# 5. First LDAP connexion used to find user DN with the filter defined before.
sub connectLDAP {
my $self = shift;
return PE_OK if ( $self->{ldap} );
my $useTls = 0;
my $tlsParam;
foreach my $server ( split /[\s,]+/, $self->{ldapServer} ) {
if ( $server =~ m{^ldap\+tls://([^/]+)/?\??(.*)$} ) {
$useTls = 1;
$server = $1;
$tlsParam = $2 || "";
}
else {
$useTls = 0;
}
last
if $self->{ldap} = Net::LDAP->new(
$server,
port => $self->{ldapPort},
onerror => undef,
);
}
return PE_LDAPCONNECTFAILED unless ( $self->{ldap} );
if ($useTls) {
my %h = split( /[&=]/, $tlsParam );
$h{cafile} = $self->{caFile} if ( $self->{caFile} );
$h{capath} = $self->{caPath} if ( $self->{caPath} );
my $mesg = $self->{ldap}->start_tls(%h);
$mesg->code && return PE_LDAPCONNECTFAILED;
}
PE_OK;
}
# 6. LDAP bind with Lemonldap::NG account or anonymous unless defined
sub bind {
my $self = shift;
$self->connectLDAP unless ( $self->{ldap} );
return PE_WRONGMANAGERACCOUNT
unless (
$self->_bind(
$self->{ldap}, $self->{managerDn}, $self->{managerPassword}
)
);
PE_OK;
}
# 7. Search the DN
sub search {
my $self = shift;
my $mesg = $self->{ldap}->search(
base => $self->{ldapBase},
scope => 'sub',
filter => $self->{filter},
);
if ( $mesg->code() != 0 ) {
print STDERR $mesg->error . "\n";
return PE_LDAPERROR;
}
return PE_USERNOTFOUND unless ( $self->{entry} = $mesg->entry(0) );
$self->{dn} = $self->{entry}->dn();
PE_OK;
}
# sub setAuthSessionInfo has to be defined in auth module
# 8. Load all parameters included in exportedVars parameter.
# Multi-value parameters are loaded in a single string with
# '; ' separator
sub setSessionInfo {
my ($self) = @_;
$self->{sessionInfo}->{dn} = $self->{dn};
$self->{sessionInfo}->{startTime} =
&POSIX::strftime( "%Y%m%d%H%M%S", localtime() );
unless ( $self->{exportedVars} ) {
foreach (qw(uid cn mail)) {
$self->{sessionInfo}->{$_} =
join( '; ', $self->{entry}->get_value($_) ) || "";
}
}
elsif ( ref( $self->{exportedVars} ) eq 'HASH' ) {
foreach ( keys %{ $self->{exportedVars} } ) {
if ( my $tmp = $ENV{$_} ) {
$tmp =~ s/[\r\n]/ /gs;
$self->{sessionInfo}->{$_} = $tmp;
}
else {
$self->{sessionInfo}->{$_} = join( '; ',
$self->{entry}->get_value( $self->{exportedVars}->{$_} ) )
|| "";
}
}
}
else {
die('Only hash reference are supported now in exportedVars');
}
PE_OK;
}
# getSessionInfo
# Read session and store it in $self
sub getSessionInfo {
my $self = shift;
my %cookies = fetch CGI::Cookie;
# Test if Lemonldap::NG cookie is available
if ( $cookies{ $self->{cookieName} }
and my $id = $cookies{ $self->{cookieName} }->value )
{
my %h;
# Trying to recover session from global session storage
eval {
tie %h, $self->{globalStorage}, $id, $self->{globalStorageOptions};
};
if ( $@ or not tied(%h) ) {
# Session not available (expired ?)
print STDERR
"Session $id isn't yet available ($ENV{REMOTE_ADDR})\n";
return undef;
}
# Store session values
foreach ( keys %h ) {
$self->{sessionInfo}->{$_} = $h{$_};
}
untie %h;
}
}
# 9. Unused here, but overloaded in SharedConf.pm
sub setMacros {
......@@ -498,15 +372,6 @@ sub setGroups {
PE_OK;
}
# 11. Now, LDAP will not be used by Lemonldap::NG except for LDAP
# authentication scheme
sub unbind {
my $self = shift;
$self->{ldap}->unbind if $self->{ldap};
delete $self->{ldap};
PE_OK;
}
# 13. Now, the user is authenticated. It's time to store his parameters with
# Apache::Session::* module
sub store {
......
package Lemonldap::NG::Portal::UserDBLDAP;
use Lemonldap::NG::Portal::Simple;
use Lemonldap::NG::Portal::_LDAP;
our $VERSION = '0.1';
sub ldap {
my $self = shift;
return $self->{ldap} || Lemonldap::NG::Portal::_LDAP->new({portal=>$self});
}
sub userDBInit {
my $self = shift;
return $self->ldap;
}
sub getUser {
my $self = shift;
return $self->_subProcess(qw(formateFilter search));
}
# 4. By default, the user is searched in the LDAP server with its UID. To use
# it with Active Directory, overload it to use CN instead of UID.
sub formateFilter {
my $self = shift;
$self->{filter} = $self->{authFilter}
|| "(&(uid=" . $self->{user} . ")(objectClass=inetOrgPerson))";
PE_OK;
}
# 7. Search the DN
sub search {
my $self = shift;
my $mesg = $self->ldap->search(
base => $self->{ldapBase},
scope => 'sub',
filter => $self->{filter},
);
if ( $mesg->code() != 0 ) {
print STDERR $mesg->error . "\n";
return PE_LDAPERROR;
}
return PE_USERNOTFOUND unless ( $self->{entry} = $mesg->entry(0) );
$self->{dn} = $self->{entry}->dn();
PE_OK;
}
# sub setAuthSessionInfo has to be defined in auth module
# 8. Load all parameters included in exportedVars parameter.
# Multi-value parameters are loaded in a single string with
# '; ' separator
sub setSessionInfo {
my ($self) = @_;
$self->{sessionInfo}->{dn} = $self->{dn};
$self->{sessionInfo}->{startTime} =
&POSIX::strftime( "%Y%m%d%H%M%S", localtime() );
unless ( $self->{exportedVars} ) {
foreach (qw(uid cn mail)) {
$self->{sessionInfo}->{$_} =
join( '; ', $self->{entry}->get_value($_) ) || "";
}
}
elsif ( ref( $self->{exportedVars} ) eq 'HASH' ) {
foreach ( keys %{ $self->{exportedVars} } ) {
if ( my $tmp = $ENV{$_} ) {
$tmp =~ s/[\r\n]/ /gs;
$self->{sessionInfo}->{$_} = $tmp;
}
else {
$self->{sessionInfo}->{$_} = join( '; ',
$self->{entry}->get_value( $self->{exportedVars}->{$_} ) )
|| "";
}
}
}
else {
die('Only hash reference are supported now in exportedVars');
}
PE_OK;
}
1;
package Lemonldap::NG::Portal::_LDAP;
use Net::LDAP;
our $VERSION = '0.1';
sub new {
my $class = shift;
my $portal = shift;
unless($portal) {
die("$class : portal argument required !");
}
my $self = bless {portal=>$portal},$class;
$self->{ldapServer} = $portal->{ldapServer} || 'localhost';
$self->{ldapPort} = $portal->{ldapPort} || 389;
$self->{ldapPpolicyControl} = $portal->{ldapPpolicyControl} || 0;
$self->{managerDn} = $portal->{managerDn};
$self->{managerPassword} = $portal->{managerPassword};
$self->bind or return 0;
return $self;
}
# Private sub used to bind to LDAP server both with Lemonldap::NG account and user
# credentials if LDAP authentication is used
sub _bind {
my ( $self, $dn, $password ) = @_;
my $mesg;
if ( $dn and $password ) { # named bind
$mesg = $self->{ldap}->bind( $dn, password => $password );
}
else { # anonymous bind
$mesg = $self->{ldap}->bind();
}
if ( $mesg->code() != 0 ) {
return 0;
}
return 1;
}
# 5. First LDAP connexion used to find user DN with the filter defined before.
sub connectLDAP {
my $self = shift;
return PE_OK if ( $self->{ldap} );
my $useTls = 0;
my $tlsParam;
foreach my $server ( split /[\s,]+/, $self->{ldapServer} ) {
if ( $server =~ m{^ldap\+tls://([^/]+)/?\??(.*)$} ) {
$useTls = 1;
$server = $1;
$tlsParam = $2 || "";
}
else {
$useTls = 0;
}
last
if $self->{ldap} = Net::LDAP->new(
$server,
port => $self->{ldapPort},
onerror => undef,
);
}
return PE_LDAPCONNECTFAILED unless ( $self->{ldap} );
if ($useTls) {
my %h = split( /[&=]/, $tlsParam );
$h{cafile} = $self->{caFile} if ( $self->{caFile} );
$h{capath} = $self->{caPath} if ( $self->{caPath} );
my $mesg = $self->{ldap}->start_tls(%h);
$mesg->code && return PE_LDAPCONNECTFAILED;
}
PE_OK;
}
sub search {
shift->{ldap}->search(@_);
}
# 6. LDAP bind with Lemonldap::NG account or anonymous unless defined
sub bind {
my $self = shift;
$self->connectLDAP unless ( $self->{ldap} );
return 0
unless (
$self->_bind(
$self->{managerDn}, $self->{managerPassword}