LEMONLDAP::NG : New auth architecture in Portal

This commit is contained in:
Xavier Guimard 2008-05-30 04:47:32 +00:00
parent f393d06a3d
commit aca6815c1b
7 changed files with 180 additions and 165 deletions

View File

@ -2,9 +2,10 @@ lemonldap-ng (0.9.2) unstable; urgency=low
* New css in manager
* cleaning Handler code
* Status system for Lemonldap::NG::Handler
* Status system for Lemonldap::NG::Handler and for the portal
* Debian Czech translation for debconf (Closes: #483301 / bugs.debian.org)
-- Xavier Guimard <x.guimard@free.fr> Fri, 09 May 2008 22:10:37 +0200
-- Xavier Guimard <x.guimard@free.fr> Wed, 28 May 2008 10:53:28 +0200
lemonldap-ng (0.9.1) unstable; urgency=low

View File

@ -62,6 +62,7 @@ t/99-pod.t
t/Lemonldap-NG-Portal-AuthApache.t
t/Lemonldap-NG-Portal-AuthCAS.t
t/Lemonldap-NG-Portal-AuthLA.t
t/Lemonldap-NG-Portal-AuthLDAP.t
t/Lemonldap-NG-Portal-AuthSSL.t
t/Lemonldap-NG-Portal-CDA.t
t/Lemonldap-NG-Portal-i18n.t

View File

@ -2,26 +2,26 @@ package Lemonldap::NG::Portal::AuthApache;
use strict;
use Lemonldap::NG::Portal::Simple;
our @ISA = qw(Lemonldap::NG::Portal::Simple);
our $VERSION = '0.01';
our $VERSION = '0.02';
our $OVERRIDE = {
# By default, authentication is valid if REMOTE_USER environment
# variable is present. Change formateFilter if this does not match with
# UID.
extractFormInfo => sub {
my $self = shift;
return PE_FORMEMPTY unless( $self->{user} = $ENV{REMOTE_USER} );
# This is needed for Kerberos authentication
$self->{user} =~ s/(.*)@(.*)/$1/g;
PE_OK;
},
# By default, authentication is valid if REMOTE_USER environment
# variable is present. Change formateFilter if this does not match with
# UID.
sub extractFormInfo {
my $self = shift;
return PE_FORMEMPTY unless ( $self->{user} = $ENV{REMOTE_USER} );
# Authentication is made by Apache.
authenticate => sub {
PE_OK;
},
};
# This is needed for Kerberos authentication
$self->{user} =~ s/(.*)@(.*)/$1/g;
PE_OK;
}
# Authentication is made by Apache.
sub authenticate {
PE_OK;
}
1;
__END__

View File

@ -2,12 +2,12 @@ package Lemonldap::NG::Portal::AuthCAS;
use strict;
use Lemonldap::NG::Portal::Simple;
our @ISA = qw(Lemonldap::NG::Portal::Simple);
use AuthCAS;
our $VERSION = '0.02';
our $VERSION = '0.03';
our $OVERRIDE = {
extractFormInfo => sub {
sub extractFormInfo {
my $self = shift;
my $cas = new AuthCAS(casUrl => $self->{CAS_url},
CAFile => $self->{CAS_CAFile},
@ -24,12 +24,11 @@ our $OVERRIDE = {
exit;
}
PE_OK;
},
}
authenticate => sub {
sub authenticate {
PE_OK;
},
};
}
1;
__END__

View File

@ -0,0 +1,77 @@
package Lemonldap::NG::Portal::AuthLDAP;
use strict;
use Lemonldap::NG::Portal::Simple;
our @ISA = qw(Lemonldap::NG::Portal::Simple);
our $VERSION = '0.1';
sub extractFormInfo {
my $self = shift;
return PE_FIRSTACCESS
unless ( $self->param('user') );
return PE_FORMEMPTY
unless ( length( $self->{'user'} = $self->param('user') ) > 0
&& length( $self->{'password'} = $self->param('password') ) > 0 );
PE_OK;
}
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} ) {
# require Perl module
eval 'require Net::LDAP::Control::PasswordPolicy';
die('Module Net::LDAP::Control::PasswordPolicy not found in @INC')
if ($@);
eval
'use Net::LDAP::Constant qw( LDAP_CONTROL_PASSWORDPOLICY LDAP_PP_ACCOUNT_LOCKED LDAP_PP_PASSWORD_EXPIRED );';
no strict 'subs';
# Create Control object
my $pp = Net::LDAP::Control::PasswordPolicy->new;
# Bind with user credentials
my $mesg = $self->{ldap}->bind(
$self->{dn},
password => $self->{password},
control => [$pp]
);
# Get bind response
return PE_OK if ( $mesg->code == 0 );
# Get server control response
my ($resp) = $mesg->control(LDAP_CONTROL_PASSWORDPOLICY);
if ( defined $resp ) {
my $pp_error = $resp->error;
if ($pp_error) {
return PE_PP_ACCOUNT_LOCKED
if ( $pp_error == LDAP_PP_ACCOUNT_LOCKED );
return PE_PP_PASSWORD_EXPIRED
if ( $pp_error == LDAP_PP_PASSWORD_EXPIRED );
}
else {
return PE_BADCREDENTIALS;
}
}
else {
return PE_LDAPERROR;
}
}
else {
return PE_BADCREDENTIALS
unless (
$self->_bind( $self->{ldap}, $self->{dn}, $self->{password} ) );
}
$self->{sessionInfo}->{authenticationLevel} = 2;
PE_OK;
}
1;

View File

@ -2,6 +2,9 @@ package Lemonldap::NG::Portal::AuthSSL;
use strict;
use Lemonldap::NG::Portal::Simple;
use Lemonldap::NG::Portal::AuthLDAP;
our @ISA = qw(Lemonldap::NG::Portal::AuthLDAP Lemonldap::NG::Portal::Simple);
our $VERSION = '0.1';
@ -9,70 +12,71 @@ our $VERSION = '0.1';
# Directory.
# So authenticate is overloaded to return only PE_OK.
our $OVERRIDE = {
# By default, authentication is valid if SSL_CLIENT_S_DN_Email environment
# variable is present. Adapt it if you want
sub extractFormInfo {
my $self = shift;
# By default, authentication is valid if SSL_CLIENT_S_DN_Email environment
# variable is present. Adapt it if you want
extractFormInfo => sub {
my $self = shift;
# Defaults values
$self->{SSLRequire} = 1 unless ( defined $self->{SSLRequire} );
$self->{SSLVar} ||= 'SSL_CLIENT_S_DN_Email';
$self->{SSLLDAPField} ||= 'mail';
# Defaults values
$self->{SSLRequire} = 1 unless ( defined $self->{SSLRequire} );
$self->{SSLVar} ||= 'SSL_CLIENT_S_DN_Email';
$self->{SSLLDAPField} ||= 'mail';
my $user = $self->https ? $ENV{ $self->{SSLVar} } : 0;
if ($user) {
$self->{sessionInfo}->{authenticationLevel} = 5;
$self->{user} = $user;
return PE_OK;
}
elsif ( $self->{SSLRequire} ) {
return PE_CERTIFICATEREQUIRED;
}
return $self->SUPER::extractFormInfo(@_);
}
my $user = $self->https ? $ENV{$self->{SSLVar}} : 0;
if ($user) {
$self->{sessionInfo}->{authenticationLevel} = 5;
$self->{user} = $user;
return PE_OK;
}
elsif ( $self->{SSLRequire} ) {
return PE_CERTIFICATEREQUIRED;
}
return $self->extractFormInfo(@_);
},
# As we know only user mail (or SSLVar), we have to use it to find him in
# the LDAP directory
sub formateFilter {
my $self = shift;
if ( $self->{sessionInfo}->{authenticationLevel}
and $self->{sessionInfo}->{authenticationLevel} > 4 )
{
$self->{filter} = '(&('
. $self->{SSLLDAPField} . '='
. $self->{user}
. ")(objectClass=person))";
return PE_OK;
}
return $self->SUPER::formateFilter(@_);
}
# As we know only user mail (or SSLVar), we have to use it to find him in
# the LDAP directory
formateFilter => sub {
my $self = shift;
if ( $self->{sessionInfo}->{authenticationLevel} and $self->{sessionInfo}->{authenticationLevel} > 4 ) {
$self->{filter} = '(&('
. $self->{SSLLDAPField} . '='
. $self->{user}
. ")(objectClass=person))";
return PE_OK;
}
return $self->formateFilter(@_);
},
# Apache SSL environment variable are available in exportedVars:
setSessionInfo => sub {
my $self = shift;
my $save = $self->{exportedVars};
if ( ref( $self->{exportedVars} ) eq 'HASH' ) {
foreach ( keys %{ $self->{exportedVars} } ) {
if (/^SSL/) {
$self->{sessionInfo}->{$_} = $ENV{$_};
delete $self->{exportedVars}->{$_};
}
# Apache SSL environment variable are available in exportedVars:
sub setSessionInfo {
my $self = shift;
my $save = $self->{exportedVars};
if ( ref( $self->{exportedVars} ) eq 'HASH' ) {
foreach ( keys %{ $self->{exportedVars} } ) {
if (/^SSL/) {
$self->{sessionInfo}->{$_} = $ENV{$_};
delete $self->{exportedVars}->{$_};
}
}
my $r = $self->setSessionInfo(@_);
$self->{exportedVars} = $save;
return $r;
},
}
my $r = $self->SUPER::setSessionInfo(@_);
$self->{exportedVars} = $save;
return $r;
}
# If authentication has been done with SSL, LDAP bind is disabled
authenticate => sub {
my $self = shift;
if ( $self->{sessionInfo}->{authenticationLevel} and $self->{sessionInfo}->{authenticationLevel} > 4 ) {
return PE_OK;
}
return $self->authenticate(@_);
},
};
# If authentication has been done with SSL, LDAP bind is disabled
sub authenticate {
my $self = shift;
if ( $self->{sessionInfo}->{authenticationLevel}
and $self->{sessionInfo}->{authenticationLevel} > 4 )
{
return PE_OK;
}
return $self->SUPER::authenticate(@_);
}
1;
__END__

View File

@ -61,21 +61,14 @@ sub new {
$self->{securedCookie} ||= 0;
$self->{cookieName} ||= "lemonldap";
$self->{ldapPpolicyControl} ||= 0;
$self->{authentication} ||= 'LDAP';
$self->{authentication} =~ s/^ldap/LDAP/;
if ( $self->{authentication} and $self->{authentication} ne "ldap" ) {
# $Lemonldap::NG::Portal::AuthSSL::OVERRIDE does not overload $self
# variables: if the administrator has defined a sub, we respect it
my $tmp =
'require Lemonldap::NG::Portal::Auth'
. $self->{authentication}
. '; $tmp = $Lemonldap::NG::Portal::Auth'
. $self->{authentication}
. '::OVERRIDE;';
eval $tmp;
die($@) if ($@);
%$self = ( %$tmp, %$self );
}
# $Lemonldap::NG::Portal::AuthSSL::OVERRIDE does not overload $self
# variables: if the administrator has defined a sub, we respect it
eval 'require Lemonldap::NG::Portal::Auth' . $self->{authentication};
die($@) if ($@);
bless $self, 'Lemonldap::NG::Portal::Auth' . $self->{authentication};
return $self;
}
@ -103,6 +96,7 @@ sub error {
# 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 = shift;
my ( $ldap, $dn, $password ) = @_;
my $mesg;
if ( $dn and $password ) { # named bind
@ -270,15 +264,8 @@ sub existingSession {
# 3. In ldap authentication scheme, we load here user and password from HTML
# form
sub extractFormInfo {
my $self = shift;
return PE_FIRSTACCESS
unless ( $self->param('user') );
return PE_FORMEMPTY
unless ( length( $self->{'user'} = $self->param('user') ) > 0
&& length( $self->{'password'} = $self->param('password') ) > 0 );
PE_OK;
}
# sub extractFormInfo has to be defined in Auth module used
# Unused. You can overload if you have to modify user and password before
# authentication
@ -290,7 +277,7 @@ sub formateParams() {
# it with Active Directory, overload it to use CN instead of UID.
sub formateFilter {
my $self = shift;
$self->{filter} = "(&(uid=" . $self->{user} . ")(objectClass=person))";
$self->{filter} = "(&(uid=" . $self->{user} . ")(objectClass=inetOrgPerson))";
PE_OK;
}
@ -333,7 +320,7 @@ sub bind {
$self->connectLDAP unless ( $self->{ldap} );
return PE_WRONGMANAGERACCOUNT
unless (
&_bind( $self->{ldap}, $self->{managerDn}, $self->{managerPassword} ) );
$self->_bind( $self->{ldap}, $self->{managerDn}, $self->{managerPassword} ) );
PE_OK;
}
@ -404,62 +391,8 @@ sub unbind {
}
# 12. Default authentication: LDAP bind with user credentials
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} ) {
# require Perl module
eval 'require Net::LDAP::Control::PasswordPolicy';
die('Module Net::LDAP::Control::PasswordPolicy not found in @INC')
if ($@);
eval
'use Net::LDAP::Constant qw( LDAP_CONTROL_PASSWORDPOLICY LDAP_PP_ACCOUNT_LOCKED LDAP_PP_PASSWORD_EXPIRED );';
no strict 'subs';
# Create Control object
my $pp = Net::LDAP::Control::PasswordPolicy->new;
# Bind with user credentials
my $mesg = $self->{ldap}->bind(
$self->{dn},
password => $self->{password},
control => [$pp]
);
# Get bind response
return PE_OK if ( $mesg->code == 0 );
# Get server control response
my ($resp) = $mesg->control(LDAP_CONTROL_PASSWORDPOLICY);
if ( defined $resp ) {
my $pp_error = $resp->error;
if ($pp_error) {
return PE_PP_ACCOUNT_LOCKED
if ( $pp_error == LDAP_PP_ACCOUNT_LOCKED );
return PE_PP_PASSWORD_EXPIRED
if ( $pp_error == LDAP_PP_PASSWORD_EXPIRED );
}
else {
return PE_BADCREDENTIALS;
}
}
else {
return PE_LDAPERROR;
}
}
else {
return PE_BADCREDENTIALS
unless ( &_bind( $self->{ldap}, $self->{dn}, $self->{password} ) );
}
$self->{sessionInfo}->{authenticationLevel} = 2;
PE_OK;
}
# sub authenticate has to be defined in Auth module used
# 13. Now, the user is authenticated. It's time to store his parameters with
# Apache::Session::* module
@ -696,7 +629,7 @@ Does nothing. To be overloaded if needed.
Creates the ldap filter using $self->{user}. By default :
$self->{filter} = "(&(uid=" . $self->{user} . ")(objectClass=person))";
$self->{filter} = "(&(uid=" . $self->{user} . ")(objectClass=inetOrgPerson))";
=head3 connectLDAP
@ -761,7 +694,7 @@ described above
=head3 _bind( $ldap, $dn, $password )
Non-object method used to bind to the ldap server.
Method used to bind to the ldap server.
=head3 header