Unverified Commit 7d1987bf authored by Francesc Guasch's avatar Francesc Guasch Committed by GitHub
Browse files

Feat(auth): LDAP advanced filters (#1561)

feat:Extra options in ldap filter

* feat(CLI): show LDAP attributes
* test(auth): check complex LDAP filter
* feat(auth): add LDAP posix users and groups
* fix(auth): match legacy SSHA

taken from 75d77433 by @jtorilo
but I am not sure about the OR part, so it just requires a match
to user name and filter

issue #1083

 Co-authored-by: Josep Martin jmartin@epsem.upc.edu
parent 6a45f898
......@@ -11,7 +11,7 @@ Ravada::Auth::LDAP - LDAP library for Ravada
use Authen::Passphrase;
use Authen::Passphrase::SaltedDigest;
use Carp qw(carp);
use Carp qw(carp croak);
use Data::Dumper;
use Digest::SHA qw(sha1_hex sha256_hex);
use Encode;
......@@ -42,6 +42,8 @@ our @OBJECT_CLASS = ('top'
,'inetOrgPerson'
);
our @OBJECT_CLASS_POSIX = (@OBJECT_CLASS,'posixAccount');
our $STATUS_EOF = 1;
our $STATUS_DISCONNECTED = 81;
our $STATUS_BAD_FILTER = 89;
......@@ -102,6 +104,106 @@ sub add_user($name, $password, $storage='rfc2307', $algorithm=undef ) {
}
}
=head2 add_user_posix
Adds a new user in the LDAP directory
Ravada::Auth::LDAP::add_user_posix($name, $password);
=cut
sub add_user_posix(%args) {
my $name = delete $args{name} or croak "Error: missing name";
my $password = delete $args{password} or croak "Error: missing password";
my $gid = (delete $args{gid} or _get_gid());
my $storage = ( delete $args{storage} or 'rfc2307');
my $algorithm = delete $args{algorithm};
confess "Error : unknown args ".dumper(\%args) if keys %args;
_init_ldap_admin();
$name = escape_filter_value($name);
$password = escape_filter_value($password);
confess "No dc base in config ".Dumper($$CONFIG->{ldap})
if !_dc_base();
my ($givenName, $sn) = $name =~ m{(\w+)\.(.*)};
my %entry = (
cn => $name
, uid => $name
, uidNumber => _new_uid()
, gidNumber => $gid
, objectClass => \@OBJECT_CLASS_POSIX
, givenName => ($givenName or $name)
, sn => ($sn or $name)
,homeDirectory => "/home/$name"
,userPassword => _password_store($password, $storage, $algorithm)
);
my $dn = "cn=$name,"._dc_base();
my $mesg = $LDAP_ADMIN->add($dn, attr => [%entry]);
if ($mesg->code) {
die "Error afegint $name to $dn ".$mesg->error;
}
}
sub _get_gid() {
my @group = search_group(name => "*");
my ($group_users) = grep { $_->get_value('cn') eq 'users' } @group;
$group_users = $group[0] if !$group_users;
if (!$group_users) {
add_group(name => 'users');
($group_users) = search_group(name => 'users');
confess "Error: I can create nor find LDAP group 'users'" if !$group_users;
}
return $group_users->get_value('gidNumber');
}
sub _new_uid($ldap=_init_ldap_admin(), $base=_dc_base()) {
my $id = 1000;
for (;;) {
my $mesg = $ldap->search( # Search for the user
base => $base,
scope => 'sub',
filter => "uidNumber=$id",
typesonly => 0,
attrs => ['*']
);
confess "LDAP error ".$mesg->code." ".$mesg->error if $mesg->code;
my @entries = $mesg->entries;
return $id if !scalar @entries;
$id++;
$id+= int(rand(10))+1;
}
}
sub _new_gid($ldap=_init_ldap_admin(), $base="ou=groups,"._dc_base() ) {
my $id = 1000;
for (;;) {
my $mesg = $ldap->search( # Search for the user
base => $base,
filter => "gidNumber=$id",
);
confess "LDAP error ".$mesg->code." ".$mesg->error if $mesg->code;
my @entries = $mesg->entries;
return $id if !scalar @entries;
$id++;
$id+= int(rand(10))+1;
}
}
sub default_object_class() {
return @OBJECT_CLASS;
}
sub _password_store($password, $storage, $algorithm) {
return _password_rfc2307($password, $algorithm) if lc($storage) eq 'rfc2307';
return _password_pbkdf2($password, $algorithm) if lc($storage) eq 'pbkdf2';
......@@ -193,6 +295,7 @@ sub search_user {
my $ldap = (delete $args{ldap} or _init_ldap_admin());
my $base = (delete $args{base} or _dc_base());
my $typesonly= (delete $args{typesonly} or 0);
my $filter_orig = delete $args{filter};
confess "ERROR: Unknown fields ".Dumper(\%args) if keys %args;
confess "ERROR: I can't connect to LDAP " if!$ldap;
......@@ -201,10 +304,11 @@ sub search_user {
$username =~ s/ /\\ /g;
my $filter = "($field=$username)";
if ( exists $$CONFIG->{ldap}->{filter} ) {
if (!defined $filter_orig && exists $$CONFIG->{ldap}->{filter} ) {
my $filter_config = $$CONFIG->{ldap}->{filter};
$filter_config = escape_filter_value($filter_config);
$filter = "(&($field=$username) ($filter_config))";
} else {
$filter = "(&($field=$username) ($filter_orig))" if $filter_orig;
}
my $mesg = $ldap->search( # Search for the user
......@@ -215,7 +319,6 @@ sub search_user {
attrs => ['*']
);
warn "LDAP retry ".$mesg->code." ".$mesg->error if $retry > 1;
if ( $retry <= 3 && $mesg->code && $mesg->code != 4 ) {
......@@ -230,6 +333,7 @@ sub search_user {
,field => $field
,retry => ++$retry
,typesonly => $typesonly
,filter => $filter_orig
);
}
......@@ -238,7 +342,9 @@ sub search_user {
return if !$mesg->count();
return $mesg->entries;
my @entries = $mesg->entries;
return $entries[0] if !wantarray;
return @entries;
}
=head2 add_group
......@@ -257,9 +363,10 @@ sub add_group {
cn => $name
,dn => "cn=$name,ou=groups,$base"
, attrs => [ cn=>$name
,objectClass => ['groupOfUniqueNames','top']
,objectClass => ['groupOfUniqueNames','top','posixGroup']
,ou => 'Groups'
,description => "Group for $name"
,gidNumber => _new_gid($LDAP_ADMIN)
]
);
if ($mesg->code) {
......@@ -309,9 +416,6 @@ sub search_group {
confess "ERROR: Unknown fields ".Dumper(\%args) if keys %args;
confess "ERROR: I can't connect to LDAP " if!$ldap;
$name = escape_filter_value($name);
my $mesg = $ldap ->search (
filter => "cn=$name"
,base => $base
......@@ -331,6 +435,7 @@ sub search_group {
);
}
my @entries = $mesg->entries;
return @entries if wantarray;
return $entries[0]
}
......@@ -482,8 +587,9 @@ sub _login_match {
# return 1 if $mesg && !$mesg->code;
# warn "ERROR: ".$mesg->code." : ".$mesg->error. " : Bad credentials for $username";
$user_ok = $self->_match_password($entry, $password);
warn $entry->dn." : $user_ok" if $Ravada::DEBUG;
eval { $user_ok = $self->_match_password($entry, $password) };
warn $@ if $@;
if ( $user_ok ) {
$self->{_ldap_entry} = $entry;
last;
......@@ -532,7 +638,8 @@ sub _match_password {
return Authen::Passphrase->from_rfc2307($password_ldap)->match($password)
if $storage =~ /rfc2307|md5/i;
return _match_pbkdf2($password_ldap,$password) if $storage =~ /pbkdf2|SSHA/i;
return _match_pbkdf2($password_ldap,$password) if $storage eq 'pbkdf2';
return _match_ssha($password_ldap,$password) if $storage eq 'SSHA';
confess "Error: storage $storage can't do match. Use bind.";
}
......@@ -544,6 +651,10 @@ sub _ntohl {
unpack('L*', pack('N*', @_));
}
sub _match_ssha($password_ldap, $password) {
return Authen::Passphrase->from_rfc2307($password_ldap)->match($password);
}
sub _match_pbkdf2($password_db_64, $password) {
my ($sign,$password_db) = $password_db_64 =~ /(\{.*?})(.*)/;
......
......@@ -288,7 +288,7 @@ sub add_user_ldap {
my $password = <STDIN>;
chomp $password;
Ravada::Auth::LDAP::add_user($login, $password);
Ravada::Auth::LDAP::add_user_posix(name => $login, password => $password);
}
sub remove_user {
......@@ -601,7 +601,19 @@ sub test_ldap {
chomp $password;
my $ok= Ravada::Auth::login( $name, $password);
if ($ok) {
print "LOGIN OK $ok->{_auth}\n";
if (!$ok->{_ldap_entry}) {
warn "No LDAP data found ".Dumper($ok->{_data});
} else {
print "LOGIN OK $ok->{_auth}\n";
print $ok->{_ldap_entry}->dn."\n";
for my $attrib (sort $ok->{_ldap_entry}->attributes ) {
my @value = $ok->{_ldap_entry}->get_value($attrib);
print "$attrib: ";
print join(",",@value);
print "\n";
}
}
} else {
print "LOGIN FAILED\n";
}
......
......@@ -17,7 +17,7 @@ use_ok('Ravada::Auth::LDAP');
my $ADMIN_GROUP = "test.admin.group";
my $RAVADA_POSIX_GROUP = "rvd_posix_group";
my $FILTER = "sn=bar";
my @FILTER = ("sn=bar", '&(sn=bar)(sn=bar)');
my ($LDAP_USER , $LDAP_PASS) = ("cn=Directory Manager","saysomething");
......@@ -315,7 +315,7 @@ sub _init_config(%arg) {
}
if ($with_filter) {
$config->{ldap}->{filter} = $FILTER;
$config->{ldap}->{filter} = $with_filter;
} else {
delete $config->{ldap}->{filter};
}
......@@ -394,9 +394,110 @@ sub _add_to_posix_group($user_name, $with_posix_group) {
ok( !$found ) if !$with_posix_group;
}
sub _create_groups {
my @groups;
my %dupe_gid;
for my $g_name0 (qw(npc wizards fighters)) {
my $g_name=new_domain_name().".$g_name0";
my $group = Ravada::Auth::LDAP::search_group(name => $g_name);
my $gid;
$gid = $group->get_value('gidNumber') if $group;
if ($group && ( !$gid || $dupe_gid{$gid}++)) {
Ravada::Auth::LDAP::_init_ldap_admin->delete($group);
$group = undef;
}
if (!$group) {
Ravada::Auth::LDAP::add_group($g_name);
$group = Ravada::Auth::LDAP::search_group(name => $g_name) or die "I can create group $g_name";
}
push @groups,($group);
}
return @groups;
}
sub _create_users(@groups) {
my @users;
my %gid_dupe;
for my $group (@groups) {
my $name = new_domain_name();
my ($user) = Ravada::Auth::LDAP::search_user(name => $name, filter => '');
if (defined $user
&& defined($user->get_value('gidNumber'))
&& $user->get_value('gidNumber') != $group->get_value('gidNumber')) {
Ravada::Auth::LDAP::_init_ldap_admin()->delete($user);
$user = undef;
}
if (!$user) {
my $gid = $group->get_value('gidNumber');
die "Error: gid $gid duplicated ".Dumper(\%gid_dupe)
if $gid_dupe{$gid};
$gid_dupe{$gid} = $group->get_value('cn');
Ravada::Auth::LDAP::add_user_posix(
name => $name
,password => "p.$name"
,gid => $gid
);
($user) = Ravada::Auth::LDAP::search_user(name => $name, filter => '');
is($user->get_value('gidNumber'),$gid,"gid wrong for user $name") or exit;
}
push @users, ($user);
}
return @users;
}
sub _init_ravada($fly_config) {
my $ravada;
$ravada = Ravada->new(config => $fly_config
, connector => connector);
$ravada->_install();
Ravada::Auth::LDAP::init();
Ravada::Auth::LDAP::_init_ldap_admin();
return $ravada;
}
sub test_filter_gid {
my $file_config = "t/etc/ravada_ldap.conf";
my $ravada = _init_ravada(_init_config(file_config => $file_config));
my @groups = _create_groups();
my @users = _create_users(@groups);
my $user_no = shift @users;
my $name = $user_no->get_value('cn');
my $user_login;
eval { $user_login = Ravada::Auth::LDAP->new(name => $name, password => "p.".$name) };
is($@, '');
ok($user_login);
my $filter = '|';
my %gid_duplicated;
for my $user ( @users ) {
my $gid = $user->get_value('gidNumber');
$filter .= "(gidNumber=$gid)";
die "Error: gid $gid duplicated ".$user->get_value('cn')."\n".Dumper(\%gid_duplicated) if $gid_duplicated{$gid};
$gid_duplicated{$gid}=$user->get_value('cn');
}
$ravada = _init_ravada(_init_config(file_config => $file_config, with_filter => $filter));
$user_login = undef;
eval { $user_login = Ravada::Auth::LDAP->new(name => $name, password => "p.".$name) };
like($@, qr(Login failed));
ok(!$user_login);
for my $user (@users) {
$name = $user->get_value('cn');
$user_login = undef;
eval { $user_login = Ravada::Auth::LDAP->new(name => $name, password => "p.".$name) };
is($@, '');
ok($user_login) or exit;
}
}
sub test_filter {
my $file_config = "t/etc/ravada_ldap.conf";
my $fly_config = _init_config(file_config => $file_config, with_filter => 1);
for my $filter (@FILTER) {
my $fly_config = _init_config(file_config => $file_config, with_filter => $filter);
SKIP: {
my $ravada;
eval { $ravada = Ravada->new(config => $fly_config
......@@ -439,6 +540,7 @@ sub test_filter {
Ravada::Auth::LDAP::remove_user($user_name);
}
}
}
sub test_posix_group {
......@@ -558,6 +660,7 @@ sub test_pass_storage($with_posix_group) {
SKIP: {
test_filter();
test_filter_gid();
my $file_config = "t/etc/ravada_ldap.conf";
for my $with_posix_group (0,1) {
for my $with_admin (0,1) {
......
Supports Markdown
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