MT#59447 admin user LDAP authentication

Change-Id: I86005af30dee6285d80fe69f020f4a33cb23a608
mr13.1
Rene Krenn 6 months ago
parent a8476ad20d
commit 358139edee

1
debian/control vendored

@ -94,6 +94,7 @@ Depends:
libmoosex-emulate-class-accessor-fast-perl,
libnamespace-autoclean-perl,
libnet-http-perl,
libnet-ldap-perl,
libnet-telnet-perl,
libnetaddr-ip-perl,
libngcp-schema-perl,

@ -1298,9 +1298,9 @@ sub dev_field_config :Chained('/') :PathPart('device/autoprov/config') :Args() {
name => 'PBX Address Book',
},
ldap => {
tls => $c->config->{ldap}->{tls},
ip => $c->config->{ldap}->{ip},
port => $c->config->{ldap}->{port},
tls => $c->config->{ldap_device}->{tls},
ip => $c->config->{ldap_device}->{ip},
port => $c->config->{ldap_device}->{port},
dn => ',dc=hpbx,dc=sipwise,dc=com', # uid=xxx,o=contract-id added below
password => '', # set below
base => ',dc=hpbx,dc=sipwise,dc=com', # o=contract-id added below

@ -14,7 +14,7 @@ has_block 'fields' => (
tag => 'div',
class => [qw(modal-body)],
render_list => [qw(
reseller login password email role_id is_master is_active read_only show_passwords call_data billing_data can_reset_password
reseller login auth_mode password email role_id is_master is_active read_only show_passwords call_data billing_data can_reset_password
)],
);

@ -10,6 +10,16 @@ sub build_render_list {[qw/submitid fields actions/]}
sub build_form_element_class {[qw(form-horizontal)]}
has_field 'login' => (type => 'Text', required => 1, minlength => 5, maxlength => 31, default_method => \&_set_default);
has_field 'auth_mode' => (
type => 'Select',
label => 'Authentication Mode',
required => 1,
options => [
{ value => 'local', label => 'local' },
{ value => 'ldap', label => 'LDAP' },
],
default_method => \&_set_default,
);
has_field 'password' => (type => 'Password', required => 1, label => 'Password');
has_field 'email' => (type => 'Email', required => 0, label => 'Email', maxlength => 255);
for (qw(is_active show_passwords call_data billing_data
@ -21,7 +31,7 @@ has_block 'fields' => (
tag => 'div',
class => [qw(modal-body)],
render_list => [qw(
login password email is_master is_active read_only show_passwords call_data billing_data can_reset_password
login auth_mode password email is_master is_active read_only show_passwords call_data billing_data can_reset_password
)],
);
has_block 'actions' => (tag => 'div', class => [qw(modal-footer)], render_list => [qw(save)],);
@ -37,6 +47,10 @@ sub _set_default {
$field->default(1);
}
if ($field->name eq 'auth_mode') {
$field->default('local');
}
if (_check_inactive($field, $field->name)) {
$field->inactive(1);
}

@ -12,7 +12,7 @@ has_block 'fields' => (
tag => 'div',
class => [qw(modal-body)],
render_list => [qw(
reseller login password email role_id is_master is_active read_only show_passwords call_data billing_data can_reset_password
reseller login auth_mode password email role_id is_master is_active read_only show_passwords call_data billing_data can_reset_password
)],
);

@ -7,6 +7,23 @@ use IO::Compress::Zip qw/zip/;
use IPC::System::Simple qw/capturex/;
use UUID;
use NGCP::Panel::Utils::Ldap qw(
auth_ldap_simple
get_user_dn
$ldapconnecterror
$ldapnouserdn
$ldapauthfailed
$ldapsearchfailed
$ldapnousersfound
$ldapmultipleusersfound
$ldapuserfound
$ldapauthsuccessful
);
our $local_auth_method = 'local';
our $ldap_auth_method = 'ldap';
our $SALT_LENGTH = 128;
our $ENCRYPT_SUBSCRIBER_WEBPASSWORDS = 1;
@ -54,7 +71,9 @@ sub get_usr_salted_pass {
sub perform_auth {
my ($c, $user, $pass, $realm, $bcrypt_realm) = @_;
my $res;
my $log_failed_login_attempt = 1;
return $res if !check_password($pass);
return $res if user_is_banned($c, $user, 'admin');
@ -64,52 +83,65 @@ sub perform_auth {
login => $user,
is_active => 1,
}) if $user;
if(defined $dbadmin && defined $dbadmin->saltedpass) {
$c->log->debug("login via bcrypt");
my $saltedpass = $dbadmin->saltedpass;
my $usr_salted_pass = get_usr_salted_pass($saltedpass, $pass);
# fetch again to load user into session etc (otherwise we could
# simply compare the two hashes here :(
$res = $c->authenticate(
{
login => $user,
saltedpass => $usr_salted_pass,
'dbix_class' => {
searchargs => [{
-and => [
login => $user,
is_active => 1,
],
}],
}
}, $bcrypt_realm
);
} elsif(defined $dbadmin) { # we already know if the username is wrong, no need to check again
# check md5 and migrate over to bcrypt on success
$c->log->debug("login via md5");
$res = $c->authenticate(
{
login => $user,
md5pass => $pass,
'dbix_class' => {
searchargs => [{
-and => [
login => $user,
is_active => 1,
],
}],
}
}, $realm);
if($res) {
# login ok, time to move user to bcrypt hashing
$c->log->debug("migrating to bcrypt");
my $saltedpass = generate_salted_hash($pass);
$dbadmin->update({
md5pass => undef,
saltedpass => $saltedpass,
});
return $res unless $dbadmin;
if ($dbadmin->auth_mode eq $local_auth_method) {
if (defined $dbadmin->saltedpass) {
$c->log->debug("login via bcrypt");
my $saltedpass = $dbadmin->saltedpass;
my $usr_salted_pass = get_usr_salted_pass($saltedpass, $pass);
# fetch again to load user into session etc (otherwise we could
# simply compare the two hashes here :(
$res = $c->authenticate(
{
login => $user,
saltedpass => $usr_salted_pass,
'dbix_class' => {
searchargs => [{
-and => [
login => $user,
is_active => 1,
],
}],
}
}, $bcrypt_realm
);
} elsif (defined $dbadmin) { # we already know if the username is wrong, no need to check again
# check md5 and migrate over to bcrypt on success
$c->log->debug("login via md5");
$res = $c->authenticate(
{
login => $user,
md5pass => $pass,
'dbix_class' => {
searchargs => [{
-and => [
login => $user,
is_active => 1,
],
}],
}
}, $realm);
if($res) {
# login ok, time to move user to bcrypt hashing
$c->log->debug("migrating to bcrypt");
my $saltedpass = generate_salted_hash($pass);
$dbadmin->update({
md5pass => undef,
saltedpass => $saltedpass,
});
}
}
} elsif ($dbadmin->auth_mode eq $ldap_auth_method) {
$c->log->debug("login via ldap");
my ($code,$message) = auth_ldap_simple($c,get_user_dn($user),$pass);
if ($code == $ldapauthfailed) {
$res = 0;
} elsif ($code != $ldapauthsuccessful) {
$res = 0;
$log_failed_login_attempt = 0; # do not log failed attempt if there was an ldap error
} else {
$res = 1;
}
}
@ -117,7 +149,7 @@ sub perform_auth {
clear_failed_login_attempts($c, $user, 'admin');
reset_ban_increment_stage($c, $user, 'admin');
}
: log_failed_login_attempt($c, $user, 'admin');
: ($log_failed_login_attempt && log_failed_login_attempt($c, $user, 'admin'));
return $res;
}

@ -0,0 +1,146 @@
package NGCP::Panel::Utils::Ldap;
use strict;
use warnings;
require Exporter;
our @ISA = qw(Exporter);
our @EXPORT_OK = qw(search_dn
auth_ldap_simple
get_user_dn
$ldapconnecterror
$ldapnouserdn
$ldapauthfailed
$ldapsearchfailed
$ldapnousersfound
$ldapmultipleusersfound
$ldapuserfound
$ldapauthsuccessful);
use Net::LDAP qw(LDAP_SUCCESS);
our $ldapconnecterror = -1;
our $ldapnouserdn = -2;
our $ldapauthfailed = -3;
our $ldapsearchfailed = -4;
our $ldapnousersfound = -5;
our $ldapmultipleusersfound = -6;
our $ldapuserfound = 1;
our $ldapauthsuccessful = 2;
sub get_user_dn {
my $c = shift;
my $dn_format = $c->config->{ldap_admin}->{format};
$dn_format ||= '%s';
print sprintf($dn_format, @_);
return sprintf($dn_format, @_);
}
sub search_dn {
my ($c,$user_dn) = @_;
my $message;
my $label = 'LDAP search: ';
my $ldap_uri = $c->config->{ldap_admin}->{uri};
my $ldap_manager_dn = $c->config->{ldap_admin}->{dn};
my $ldap_manager_password = $c->config->{ldap_admin}->{password};
if (length($user_dn)) {
my $ldap = Net::LDAP->new($ldap_uri, verify => 'none');
if (defined $ldap) {
my $mesg;
if (length($ldap_manager_dn) > 0) {
$mesg = $ldap->bind($ldap_manager_dn, password => $ldap_manager_password);
} else {
$mesg = $ldap->bind();
}
if ($mesg->code() != LDAP_SUCCESS) {
$message = $mesg->error();
$c->log->debug($label . $message);
return ($ldapauthfailed, $message);
}
my $search = $ldap->search(base => $user_dn, scope => 'base', filter => '(objectClass=*)'); #attrs => ['dn'], );
if ($search->code() != LDAP_SUCCESS) {
$message = $search->error();
$c->log->debug($label . $message);
return ($ldapsearchfailed,$message);
}
if ($search->count() == 0) {
$message = 'no ldap entry found: ' . $user_dn;
$ldap->unbind();
$c->log->debug($label . $message);
return ($ldapnousersfound,$message);
} elsif ($search->count() > 1) {
$message = 'multiple ldap entries found: ' . $user_dn;
$ldap->unbind();
$c->log->debug($label . $message);
return ($ldapmultipleusersfound,$message);
} else {
my $entry = $search->shift_entry();
$message = 'ldap entry found: ' . $entry->dn();
$ldap->unbind();
$c->log->info($label . $message);
return ($ldapuserfound,$message);
}
} else {
$message = $@;
$c->log->debug($label . $message);
return ($ldapconnecterror,$message);
}
} else {
$message = 'no user dn specified';
$c->log->debug($label . $message);
return ($ldapnouserdn,$message);
}
}
sub auth_ldap_simple {
my ($c,$user_dn,$password) = @_;
my $label = 'LDAP auth: ';
my $ldap_uri = $c->config->{ldap}->{admins}->{uri};
my $message = undef;
if (length($user_dn) > 0) {
my $ldap = Net::LDAP->new($ldap_uri, verify => 'none');
if (defined $ldap) {
my $mesg = $ldap->bind($user_dn, password => $password);
if ($mesg->code() != LDAP_SUCCESS) {
$message = $mesg->error();
$c->log->debug($label . $message);
return ($ldapauthfailed,$message);
} else {
$message = 'successful ldap authentication: ' . $user_dn;
$c->log->info($label . $message);
}
$ldap->unbind();
return ($ldapauthsuccessful,$message);
} else {
$message = $@;
$c->log->debug($label . $message);
return ($ldapconnecterror,$message);
}
} else {
$message = 'no user dn specified';
$c->log->debug($label . $message);
return ($ldapnouserdn,$message);
}
}
1;
Loading…
Cancel
Save