MT#59449 enhance password validation and handling

* passwords are now validated based on
  - minlen
  - maxlen
  - min lower case chars
  - min uppper case chars
  - min digits
  - min special chars
* Data::Password::zxcvbn is used to calculate
  password score and reject passwords with score < 3 as weak
  (this library is ported from the Dropbox password validation)
* Add password journals and check last used passwords in the journals
* Improve password generator javascript function to generate a password
  with at least 4 of each of the char group types.
* Currently affected are subcriber and admin entry creation or
  modification via UI/API
* NGCP::Utils::Auth add optional bcrypt_cost support as last argument
  for generate_salted_hash and get_usr_salted_pass

Change-Id: I100c25107d91741d5101bc58d29a3fa558b0b017
mr12.5
Kirill Solomko 9 months ago
parent 43d112bd5e
commit d9f283cbc8

1
debian/control vendored

@ -46,6 +46,7 @@ Depends:
libdata-hal-perl,
libdata-ical-perl,
libdata-page-perl,
libdata-password-zxcvbn-perl,
libdata-printer-perl,
libdata-serializer-perl,
libdata-structure-util-perl,

@ -57,7 +57,12 @@ sub create_item {
my $item;
try {
my $pass = delete $resource->{password};
$item = $c->model('DB')->resultset('admins')->create($resource);
NGCP::Panel::Utils::Admin::insert_password_journal(
$c, $item, $pass
);
} catch($e) {
$self->error($c, HTTP_INTERNAL_SERVER_ERROR, "Failed to create admin.", $e);
return;

@ -66,8 +66,8 @@ sub GET :Allow {
$config{file} = $c->config->{'Plugin::ConfigLoader'}->{file};
$config{numbermanagement}->{auto_sync_cli} = $config_internal{numbermanagement}->{auto_sync_cli};
$config{numbermanagement}->{auto_allow_cli} = $config_internal{numbermanagement}->{auto_allow_cli};
$config{security}->{password_web_validate} = $config_internal{security}->{password_web_validate};
$config{security}->{password_sip_validate} = $config_internal{security}->{password_sip_validate};
$config{security}->{password}->{web_validate} = $config_internal{security}->{password}{web_validate};
$config{security}->{password}->{sip_validate} = $config_internal{security}->{password}{sip_validate};
$config{privileges} = $config_internal{privileges};
$config{features} = $config_internal{features};

@ -401,6 +401,8 @@ sub POST :Allow {
my $license_max_pbx_groups = $c->license_max_pbx_groups;
my $current_pbx_groups_count = $c->license_current_pbx_groups;
$c->log->debug("Current pbx groups: ". $current_pbx_groups_count);
$c->log->debug("License max pbx groups: ". $license_max_pbx_groups);
if (is_true($resource->{is_pbx_group}) &&
$license_max_pbx_groups >= 0 && $current_pbx_groups_count >= $license_max_pbx_groups) {
$self->error($c, HTTP_FORBIDDEN,

@ -84,6 +84,7 @@ sub PUT :Allow {
my $balance = NGCP::Panel::Utils::ProfilePackages::get_contract_balance(c => $c,
contract => $subscriber->contract,
); #apply underrun lock level
$c->stash->{subscriber} = $subscriber; # password validation
my $resource = $self->get_valid_put_data(
c => $c,
id => $id,
@ -126,6 +127,7 @@ sub PATCH :Allow {
my $balance = NGCP::Panel::Utils::ProfilePackages::get_contract_balance(c => $c,
contract => $subscriber->contract,
); #apply underrun lock level
$c->stash->{subscriber} = $subscriber; # password validation
my $json = $self->get_valid_patch_data(
c => $c,
id => $id,

@ -5,6 +5,7 @@ use parent 'Catalyst::Controller';
use NGCP::Panel::Form;
use HTTP::Headers qw();
use NGCP::Panel::Utils::Admin;
use NGCP::Panel::Utils::Message;
use NGCP::Panel::Utils::Navigation;
use NGCP::Panel::Utils::Auth;
@ -147,8 +148,9 @@ sub create :Chained('list_admin') :PathPart('create') :Args(0) :AllowedRole(admi
} else {
$form->values->{reseller_id} = $c->user->reseller_id;
}
my $password = delete $form->values->{password};
$form->values->{md5pass} = undef;
$form->values->{saltedpass} = NGCP::Panel::Utils::Auth::generate_salted_hash(delete $form->values->{password});
$form->values->{saltedpass} = NGCP::Panel::Utils::Auth::generate_salted_hash($password);
if ($form->values->{role_id}) {
$form->values->%* = ( $form->values->%*, NGCP::Panel::Utils::UserRole::resolve_flags($c, $form->values->{role_id}) );
@ -156,8 +158,13 @@ sub create :Chained('list_admin') :PathPart('create') :Args(0) :AllowedRole(admi
$form->values->{role_id} = NGCP::Panel::Utils::UserRole::resolve_role_id($c, $form->values);
}
$c->stash->{admins}->create($form->values);
my $admin = $c->stash->{admins}->create($form->values);
delete $c->session->{created_objects}->{reseller};
NGCP::Panel::Utils::Admin::insert_password_journal(
$c, $admin, $password
);
NGCP::Panel::Utils::Message::info(
c => $c,
desc => $c->loc('Administrator successfully created'),
@ -254,9 +261,10 @@ sub edit :Chained('base') :PathPart('edit') :Args(0) {
delete $form->values->{reseller};
}
delete $form->values->{password} unless length $form->values->{password};
if(exists $form->values->{password}) {
my $password = delete $form->values->{password} // undef;
if ($password) {
$form->values->{md5pass} = undef;
$form->values->{saltedpass} = NGCP::Panel::Utils::Auth::generate_salted_hash(delete $form->values->{password});
$form->values->{saltedpass} = NGCP::Panel::Utils::Auth::generate_salted_hash($password);
}
#should be after other fields, to remove all added values, e.g. reseller_id
if($c->stash->{administrator}->login eq NGCP::Panel::Utils::Auth::get_special_admin_login()) {
@ -276,6 +284,13 @@ sub edit :Chained('base') :PathPart('edit') :Args(0) {
$c->stash->{administrator}->update($form->values);
delete $c->session->{created_objects}->{reseller};
if ($password) {
NGCP::Panel::Utils::Admin::insert_password_journal(
$c, $c->stash->{administrator}, $password
);
}
NGCP::Panel::Utils::Message::info(
c => $c,
data => { $c->stash->{administrator}->get_inflated_columns },

@ -458,7 +458,7 @@ sub reset_webpassword_nosubscriber :Chained('/') :PathPart('resetwebpassword') :
my ($self, $c) = @_;
$c->detach('/denied_page')
unless($c->config->{security}->{password_allow_recovery});
unless($c->config->{security}->{password}{allow_recovery});
my $posted = $c->req->method eq "POST";
my $form = NGCP::Panel::Form::get("NGCP::Panel::Form::Subscriber::RecoverPassword", $c);
@ -3031,8 +3031,22 @@ sub edit_master :Chained('master') :PathPart('edit') :Args(0) :Does(ACL) :ACLDet
}
}
my $prev_password = $prov_subscriber->password;
$prov_subscriber->update($prov_params);
if ($form->params->{password} && $form->params->{password} ne $prev_password) {
NGCP::Panel::Utils::Subscriber::insert_password_journal(
$c, $prov_subscriber, $form->params->{password}
);
}
if ($form->params->{webpassword}) {
NGCP::Panel::Utils::Subscriber::insert_webpassword_journal(
$c, $prov_subscriber, $form->params->{webpassword}
);
}
my $new_group_ids = defined $form->value->{group_select} ?
decode_json($form->value->{group_select}) : [];
NGCP::Panel::Utils::Subscriber::manage_pbx_groups(

@ -61,12 +61,12 @@ override 'update_fields' => sub {
}
if($c->config->{security}->{password_sip_autogenerate}) {
if($c->config->{security}->{password}->{sip_autogenerate}) {
# todo: only set to inactive for certain roles, and only if specified in config
$self->field('password')->inactive(1);
$self->field('password')->required(0);
}
if($c->config->{security}->{password_web_autogenerate}) {
if($c->config->{security}->{password}->{web_autogenerate}) {
# todo: only set to inactive for certain roles, and only if specified in config
$self->field('webpassword')->inactive(1);
$self->field('webpassword')->required(0);

@ -17,10 +17,10 @@ override 'update_fields' => sub {
super();
if($c->user->roles eq "subscriberadmin") {
if(!$c->config->{security}->{password_sip_expose_subadmin}) {
if(!$c->config->{security}->{password}->{sip_expose_subadmin}) {
$self->field('password')->inactive(1);
}
if(!$c->config->{security}->{password_web_expose_subadmin}) {
if(!$c->config->{security}->{password}->{web_expose_subadmin}) {
$self->field('webpassword')->inactive(1);
}
}

@ -24,10 +24,10 @@ sub update_fields {
}
if($c->user->roles eq "subscriberadmin") {
if(!$c->config->{security}->{password_sip_expose_subadmin}) {
if(!$c->config->{security}->{password}->{sip_expose_subadmin}) {
$self->field('password')->inactive(1);
}
if(!$c->config->{security}->{password_web_expose_subadmin}) {
if(!$c->config->{security}->{password}->{web_expose_subadmin}) {
$self->field('webpassword')->inactive(1);
}
}

@ -54,11 +54,11 @@ sub update_fields {
);
}
if($c->config->{security}->{password_sip_autogenerate}) {
if($c->config->{security}->{password}->{sip_autogenerate}) {
$self->field('password')->inactive(1);
$self->field('password')->required(0);
}
if($c->config->{security}->{password_web_autogenerate}) {
if($c->config->{security}->{password}->{web_autogenerate}) {
$self->field('webpassword')->inactive(1);
$self->field('webpassword')->required(0);
}

@ -242,11 +242,11 @@ sub update_fields {
);
}
if($c->config->{security}->{password_sip_autogenerate} || !$c->user->show_passwords) {
if($c->config->{security}->{password}->{sip_autogenerate} || !$c->user->show_passwords) {
$self->field('password')->inactive(1);
$self->field('password')->required(0);
}
if($c->config->{security}->{password_web_autogenerate} || !$c->user->show_passwords) {
if($c->config->{security}->{password}->{web_autogenerate} || !$c->user->show_passwords) {
$self->field('webpassword')->inactive(1);
$self->field('webpassword')->required(0);
}

@ -167,11 +167,11 @@ sub update_fields {
);
}
if($c->config->{security}->{password_sip_autogenerate}) {
if($c->config->{security}->{password}->{sip_autogenerate}) {
$self->field('password')->inactive(1);
$self->field('password')->required(0);
}
if($c->config->{security}->{password_web_autogenerate}) {
if($c->config->{security}->{password}->{web_autogenerate}) {
$self->field('webpassword')->inactive(1);
$self->field('webpassword')->required(0);
}

@ -202,11 +202,11 @@ sub update_fields {
my $c = $self->ctx;
return unless $c;
if($c->config->{security}->{password_sip_autogenerate} and $self->field('password')) {
if($c->config->{security}->{password}->{sip_autogenerate} and $self->field('password')) {
$self->field('password')->inactive(1);
$self->field('password')->required(0);
}
if($c->config->{security}->{password_web_autogenerate} and $self->field('webpassword')) {
if($c->config->{security}->{password}->{web_autogenerate} and $self->field('webpassword')) {
$self->field('webpassword')->inactive(1);
$self->field('webpassword')->required(0);
}

@ -245,10 +245,10 @@ sub update_fields {
# make sure we don't use contract, as we have customer
$self->field('contract')->inactive(1);
if($c->config->{security}->{password_sip_autogenerate}) {
if($c->config->{security}->{password}->{sip_autogenerate}) {
$self->field('password')->required(0);
}
if($c->config->{security}->{password_web_autogenerate}) {
if($c->config->{security}->{password}->{web_autogenerate}) {
$self->field('webpassword')->required(0);
}
return;

@ -333,11 +333,11 @@ sub update_fields {
# make sure we don't use contract, as we have customer
$self->field('contract')->inactive(1);
if($c->config->{security}->{password_sip_autogenerate} && $self->field('password')) {
if($c->config->{security}->{password}->{sip_autogenerate} && $self->field('password')) {
$self->field('password')->inactive(1);
$self->field('password')->required(0);
}
if($c->config->{security}->{password_web_autogenerate} && $self->field('webpassword')) {
if($c->config->{security}->{password}->{web_autogenerate} && $self->field('webpassword')) {
$self->field('webpassword')->inactive(1);
$self->field('webpassword')->required(0);
}

@ -92,7 +92,6 @@ sub process_form_resource {
NGCP::Panel::Utils::API::apply_resource_reseller_id($c, $resource);
my $pass = $resource->{password};
delete $resource->{password};
if (defined $pass) {
$resource->{md5pass} = undef;
$resource->{saltedpass} = NGCP::Panel::Utils::Auth::generate_salted_hash($pass);
@ -195,6 +194,11 @@ sub update_item {
$self->error($c, HTTP_FORBIDDEN, "Only own user can change password");
return;
}
NGCP::Panel::Utils::Admin::insert_password_journal(
$c, $item, $pass
);
$resource->{md5pass} = undef;
$resource->{saltedpass} = NGCP::Panel::Utils::Auth::generate_salted_hash($pass);
}

@ -212,10 +212,10 @@ sub resource_from_item {
if ($c->user->roles eq "subscriberadmin") {
$resource{customer_id} = $contract_id;
if ($item->id != $c->user->voip_subscriber->id) {
if (!$c->config->{security}->{password_sip_expose_subadmin}) {
if (!$c->config->{security}->{password}->{sip_expose_subadmin}) {
delete $resource{_password};
}
if (!$c->config->{security}->{password_web_expose_subadmin}) {
if (!$c->config->{security}->{password}->{web_expose_subadmin}) {
delete $resource{_webpassword};
}
}
@ -530,6 +530,18 @@ sub update_item {
contact_id => $resource->{contact_id},
};
if ($resource->{password} && $resource->{password} ne $prov_subscriber->password) {
NGCP::Panel::Utils::Subscriber::insert_password_journal(
$c, $prov_subscriber, $resource->{password}
);
}
if ($resource->{webpassword}) {
NGCP::Panel::Utils::Subscriber::insert_webpassword_journal(
$c, $prov_subscriber, $resource->{webpassword}
);
}
if (exists $resource->{webpassword} and $NGCP::Panel::Utils::Auth::ENCRYPT_SUBSCRIBER_WEBPASSWORDS) {
$resource->{webpassword} = NGCP::Panel::Utils::Auth::generate_salted_hash($resource->{webpassword});
}

@ -0,0 +1,65 @@
package NGCP::Panel::Utils::Admin;
use strict;
use warnings;
use Sipwise::Base;
use NGCP::Panel::Utils::Generic qw(:all);
use DBIx::Class::Exception;
use NGCP::Panel::Utils::Auth;
use HTTP::Status qw(:constants);
sub insert_password_journal {
my ($c, $admin, $password) = @_;
my $bcrypt_cost = 6;
my $keep_last_used = $c->config->{security}{password}{web_keep_last_used} // return;
my $rs = $admin->last_passwords->search({
},{
order_by => { '-desc' => 'created_at' },
});
my @delete_ids = ();
my $idx = 0;
foreach my $row ($rs->all) {
$idx++;
$idx >= $keep_last_used ? push @delete_ids, $row->id : next;
}
my $del_rs = $rs->search({
id => { -in => \@delete_ids },
});
$del_rs->delete;
$admin->last_passwords->create({
admin_id => $admin->id,
value => NGCP::Panel::Utils::Auth::generate_salted_hash($password, $bcrypt_cost),
});
$admin->update({ saltedpass_modify_timestamp => \'current_timestamp()' });
}
1;
=head1 NAME
NGCP::Panel::Utils::Admin
=head1 DESCRIPTION
A temporary helper to manipulate admin data
=head1 AUTHOR
Sipwise Development Team <support@sipwise.com>
=head1 LICENSE
This library is free software. You can redistribute it and/or modify
it under the same terms as Perl itself.
=cut
# vim: set tabstop=4 expandtab:

@ -30,24 +30,25 @@ sub get_bcrypt_cost {
sub generate_salted_hash {
my $pass = shift;
my $bcrypt_cost = shift // get_bcrypt_cost();
my $salt = rand_bits($SALT_LENGTH);
my $b64salt = en_base64($salt);
my $b64hash = en_base64(bcrypt_hash({
key_nul => 1,
cost => get_bcrypt_cost(),
cost => $bcrypt_cost,
salt => $salt,
}, $pass));
return $b64salt . '$' . $b64hash;
}
sub get_usr_salted_pass {
my ($saltedpass, $pass) = @_;
my ($saltedpass, $pass, $opt_cost) = @_;
my ($db_b64salt, $db_b64hash) = split /\$/, $saltedpass;
my $salt = de_base64($db_b64salt);
my $usr_b64hash = en_base64(bcrypt_hash({
key_nul => 1,
cost => get_bcrypt_cost(),
cost => $opt_cost // get_bcrypt_cost(),
salt => $salt,
}, $pass));
return $db_b64salt . '$' . $usr_b64hash;

@ -1,19 +1,19 @@
package NGCP::Panel::Utils::Form;
use Sipwise::Base;
use Crypt::Cracklib;
use Data::Password::zxcvbn qw(password_strength);
use NGCP::Panel::Utils::Auth;
sub validate_password {
my %params = @_;
my $c = $params{c};
my $field = $params{field};
my $r = $c->config->{security};
my $pw = $c->config->{security}{password};
my $utf8 = $params{utf8} // 1;
my $pass = $field->value;
my $minlen = $r->{password_min_length} // 6;
my $maxlen = $r->{password_max_length} // 40;
my $minlen = $pw->{min_length} // 12;
my $maxlen = $pw->{max_length} // 40;
if(length($pass) < $minlen) {
$field->add_error($c->loc('Must be at minimum [_1] characters long', $minlen));
@ -21,46 +21,101 @@ sub validate_password {
if(length($pass) > $maxlen) {
$field->add_error($c->loc('Must be at maximum [_1] characters long', $maxlen));
}
if($r->{password_musthave_lowercase} && $pass !~ /[a-z]/) {
$field->add_error($c->loc('Must contain lower-case characters'));
if ($pass =~ /\s/) {
$field->add_error($c->loc("Must not contain spaces"));
}
if($r->{password_musthave_uppercase} && $pass !~ /[A-Z]/) {
$field->add_error($c->loc('Must contain upper-case characters'));
if(my $c_check = $pw->{musthave_lowercase}) {
my $count = 0;
map { $_ =~ /^[a-z]$/ and $count++ } split(//, $pass);
if ($count < $c_check) {
$field->add_error($c->loc("Must contain at least $c_check lower-case characters"));
}
}
if(my $c_check = $pw->{musthave_uppercase}) {
my $count = 0;
map { $_ =~ /^[A-Z]$/ and $count++ } split(//, $pass);
if ($count < $c_check) {
$field->add_error($c->loc("Must contain at least $c_check upper-case characters"));
}
}
if($r->{password_musthave_digit} && $pass !~ /[0-9]/) {
$field->add_error($c->loc('Must contain digits'));
if(my $c_check = $pw->{musthave_digit}) {
my $count = 0;
map { $_ =~ /^[0-9]$/ and $count++ } split(//, $pass);
if ($count < $c_check) {
$field->add_error($c->loc("Must contain at least $c_check digits"));
}
}
if($r->{password_musthave_specialchar} && $pass !~ /[^0-9a-zA-Z]/) {
$field->add_error($c->loc('Must contain special characters'));
if(my $c_check = $pw->{musthave_specialchar}) {
my $count = 0;
map { $_ =~ /^[^0-9a-zA-Z]$/ and $count++ } split(//, $pass);
if ($count < $c_check) {
$field->add_error($c->loc("Must contain at least $c_check special characters"));
}
}
if (!$utf8 && $pass && !NGCP::Panel::Utils::Auth::check_password($pass)) {
$field->add_error($c->loc('Contains invalid characters'));
}
if($field->name eq "password" && $r->{password_sip_validate}) {
my $res = password_strength($pass);
if ($res->{score} < 3) {
$field->add_error($c->loc('Password is too weak'));
}
my $lp_rs;
my $check_last_passwords = 0;
my $prov_sub = $c->stash->{subscriber}
? $c->stash->{subscriber}->provisioning_voip_subscriber
: undef;
my $admin = $c->stash->{administrator} // undef;
if($field->name eq "password" && $pw->{sip_validate}) {
my $user;
if($field->form->field('username')) {
$user = $field->form->field('username')->value;
} elsif($c->stash->{subscriber}) {
$user = $c->stash->{subscriber}->provisioning_voip_subscriber->username;
} elsif($prov_sub) {
$user = $prov_sub->username;
if (defined $user && $pass =~ /$user/i) {
$field->add_error($c->loc('Must not contain username'));
}
} elsif($admin) {
$user = $admin->login;
if (defined $user && $pass =~ /$user/i) {
$field->add_error($c->loc('Must not contain login'));
}
}
if(defined $user && $pass =~ /$user/i) {
$field->add_error($c->loc('Must not contain username'));
if ($pass && $prov_sub && $pass ne $prov_sub->password) {
$lp_rs = $prov_sub->last_passwords;
$check_last_passwords = 1;
}
unless(Crypt::Cracklib::check($pass)) {
$field->add_error($c->loc('Password is too weak'));
if ($pass && $admin) {
$lp_rs = $admin->last_passwords;
$check_last_passwords = 1;
}
} elsif($field->name eq "webpassword" && $r->{password_web_validate}) {
} elsif($field->name eq "webpassword" && $pw->{web_validate}) {
my $user;
if($field->form->field('webusername')) {
$user = $field->form->field('webusername')->value;
} elsif($c->stash->{subscriber}) {
$user = $c->stash->{subscriber}->provisioning_voip_subscriber->webusername;
} elsif($prov_sub) {
$user = $prov_sub->webusername;
}
if(defined $user && $pass =~ /$user/i) {
$field->add_error($c->loc('Must not contain username'));
}
unless(Crypt::Cracklib::check($pass)) {
$field->add_error($c->loc('Password is too weak'));
if ($pass && $prov_sub) {
$lp_rs = $prov_sub->last_webpasswords;
$check_last_passwords = 1;
}
}
if ($check_last_passwords) {
my $bcrypt_cost = 6;
foreach my $row ($lp_rs->all) {
my $last_password = $row->value;
my $enc_pass = $NGCP::Panel::Utils::Auth::ENCRYPT_SUBSCRIBER_WEBPASSWORDS
? NGCP::Panel::Utils::Auth::get_usr_salted_pass($last_password, $pass, $bcrypt_cost)
: $pass;
if ($last_password eq $enc_pass) {
$field->add_error($c->loc('Password was previously used'));
last;
}
}
}
}

@ -8,6 +8,7 @@ use NGCP::Panel::Utils::Generic qw(:all);
use DBIx::Class::Exception;
use String::MkPasswd;
use NGCP::Panel::Utils::Auth;
use NGCP::Panel::Utils::DateTime;
use NGCP::Panel::Utils::Preferences;
use NGCP::Panel::Utils::Email;
@ -643,20 +644,20 @@ sub create_subscriber {
die("invalid timezone name '$params->{timezone}' detected");
}
my $passlen = $c->config->{security}->{password_min_length} || 8;
if($c->config->{security}->{password_sip_autogenerate} and not defined $params->{password}) {
my $passlen = $c->config->{security}->{password}->{min_length} || 12;
if($c->config->{security}->{password}->{sip_autogenerate} and not defined $params->{password}) {
$params->{password} = String::MkPasswd::mkpasswd(
-length => $passlen,
-minnum => 1, -minlower => 1, -minupper => 1, -minspecial => 1,
-minnum => 3, -minlower => 3, -minupper => 3, -minspecial => 3,
-distribute => 1, -fatal => 1,
);
#otherwise it breaks xml device configs
$params->{password} =~s/[<>&]/,/g;
}
if($c->config->{security}->{password_web_autogenerate} and not defined $params->{webpassword}) {
if($c->config->{security}->{password}->{web_autogenerate} and not defined $params->{webpassword}) {
$params->{webpassword} = String::MkPasswd::mkpasswd(
-length => $passlen,
-minnum => 1, -minlower => 1, -minupper => 1, -minspecial => 1,
-minnum => 3, -minlower => 3, -minupper => 3, -minspecial => 3,
-distribute => 1, -fatal => 1,
);
}
@ -697,12 +698,15 @@ sub create_subscriber {
primary_number_id => undef, # will be filled in next step
contact_id => $contact ? $contact->id : undef,
});
unless(exists $params->{password}) {
my ($pass_bin, $pass_str);
UUID::generate($pass_bin);
UUID::unparse($pass_bin, $pass_str);
$params->{password} = $pass_str;
}
my $raw_webpassword = $params->{webpassword} // undef;
if (exists $params->{webpassword} and $NGCP::Panel::Utils::Auth::ENCRYPT_SUBSCRIBER_WEBPASSWORDS) {
$params->{webpassword} = NGCP::Panel::Utils::Auth::generate_salted_hash($params->{webpassword});
}
@ -726,6 +730,18 @@ sub create_subscriber {
create_timestamp => NGCP::Panel::Utils::DateTime::current_local,
});
if ($params->{password}) {
NGCP::Panel::Utils::Subscriber::insert_password_journal(
$c, $prov_subscriber, $params->{password}
);
}
if ($raw_webpassword) {
NGCP::Panel::Utils::Subscriber::insert_webpassword_journal(
$c, $prov_subscriber, $raw_webpassword,
);
}
my $aliases_before = NGCP::Panel::Utils::Events::get_aliases_snapshot(
c => $c,
schema => $schema,
@ -2674,6 +2690,73 @@ sub get_subscribers_count {
return $rs->count();
}
sub insert_password_journal {
my ($c, $prov_sub, $password) = @_;
my $bcrypt_cost = 6;
my $keep_last_used = $c->config->{security}{password}{sip_keep_last_used} // return;
my $rs = $prov_sub->last_passwords->search({
},{
order_by => { '-desc' => 'created_at' },
});
my @delete_ids = ();
my $idx = 0;
foreach my $row ($rs->all) {
$idx++;
$idx >= $keep_last_used ? push @delete_ids, $row->id : next;
}
my $del_rs = $rs->search({
id => { -in => \@delete_ids },
});
$del_rs->delete;
$prov_sub->last_passwords->create({
subscriber_id => $prov_sub->id,
value => $NGCP::Panel::Utils::Auth::ENCRYPT_SUBSCRIBER_WEBPASSWORDS
? NGCP::Panel::Utils::Auth::generate_salted_hash($password, $bcrypt_cost)
: $password,
});
$prov_sub->update({ password_modify_timestamp => \'current_timestamp()' });
}
sub insert_webpassword_journal {
my ($c, $prov_sub, $webpassword) = @_;
my $bcrypt_cost = 6;
my $keep_last_used = $c->config->{security}{password}{web_keep_last_used} // return;
my $rs = $prov_sub->last_webpasswords->search({
},{
order_by => { '-desc' => 'created_at' },
});
my @delete_ids = ();
my $idx = 0;
foreach my $row ($rs->all) {
$idx++;
$idx >= $keep_last_used ? push @delete_ids, $row->id : next;
}
my $del_rs = $rs->search({
id => { -in => \@delete_ids },
});
$del_rs->delete;
$prov_sub->last_webpasswords->create({
subscriber_id => $prov_sub->id,
value => $NGCP::Panel::Utils::Auth::ENCRYPT_SUBSCRIBER_WEBPASSWORDS
? NGCP::Panel::Utils::Auth::generate_salted_hash($webpassword, $bcrypt_cost)
: $webpassword,
});
$prov_sub->update({ webpassword_modify_timestamp => \'current_timestamp()' });
}
1;
=head1 NAME

@ -1,10 +1,27 @@
function generate_password(len) {
var text = "";
var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!?/-_%$()[]";
for (var i = 0; i < len; i++) {
text += possible.charAt(Math.floor(Math.random() * possible.length));
var possible_lower = "abcdefghijklmnopqrstuvwxyz";
var possible_upper = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
var possible_digit = "0123456789";
var possible_spec = "@!?/\-_&*^%$;:<>,.()[]{}";
var min_lower = 4;
var min_upper = 4;
var min_digit = 4;
var min_spec = 4;
var chars = ''
for (var i = 0; i < min_lower; i++) {
chars += possible_lower.charAt(Math.floor(Math.random() * possible_lower.length));
}
return text;
for (var i = 0; i < min_upper; i++) {
chars += possible_upper.charAt(Math.floor(Math.random() * possible_upper.length));
}
for (var i = 0; i < min_digit; i++) {
chars += possible_digit.charAt(Math.floor(Math.random() * possible_digit.length));
}
for (var i = 0; i < min_spec; i++) {
chars += possible_spec.charAt(Math.floor(Math.random() * possible_spec.length));
}
return [...chars].sort(()=>Math.random()-.5).join('');
}
$(document).ready(function() {
var btn = '<div id="gen_password" class="btn btn-primary pull-right" style="width:10%">Generate</div>';

Loading…
Cancel
Save