MT#60858 add /api/passwordchange endpoint

* the new endpoint accepts new_password fields and enables
  for authenticated user a mechanism to quickly change their password
* the global password validation rules are enabled
* returns 204 No Content
* if user's password is expired, the endpoint is the only accessible
  for the user to change the password and unlock other endpoints.
* a few fixes in validate_password() to correctly fetch provisioning
  subscriber for password change scenarios and webpassword field

Change-Id: I906fcfe5c780b850d322b46b445b54c054767673
mr13.0
Kirill Solomko 1 year ago
parent e09de1e781
commit bbd44f16fa

@ -0,0 +1,95 @@
package NGCP::Panel::Controller::API::PasswordChange;
use NGCP::Panel::Utils::Generic qw(:all);
use Sipwise::Base;
use boolean qw(true);
use Data::HAL qw();
use Data::HAL::Link qw();
use HTTP::Headers qw();
use HTTP::Status qw(:constants);
use NGCP::Panel::Utils::Auth;
use NGCP::Panel::Utils::Admin;
use NGCP::Panel::Utils::Subscriber;
sub allowed_methods{
return [qw/POST OPTIONS/];
}
use parent qw/NGCP::Panel::Role::Entities NGCP::Panel::Role::API::PasswordChange/;
sub api_description {
return 'Change password of the authenticated user.';
}
sub query_params {
return [
];
}
sub resource_name{
return 'passwordchange';
}
sub dispatch_path{
return '/api/passwordchange/';
}
sub relation{
return 'http://purl.org/sipwise/ngcp-api/#rel-passwordchange';
}
__PACKAGE__->set_config({
action => {
map { $_ => {
Args => 0,
Does => [qw(CheckTrailingSlash RequireSSL)],
Method => $_,
Path => __PACKAGE__->dispatch_path,
} } @{ __PACKAGE__->allowed_methods },
},
});
sub return_representation_post {}
sub create_item {
my ($self, $c, $resource, $form, $process_extras) = @_;
my $item = $c->user;
try {
require Data::Dumper;
print Data::Dumper->Dumpxs([$resource]);
my $new_password = $resource->{new_password} // '';
my $ngcp_realm = $c->request->env->{NGCP_REALM} // 'admin';
if ($ngcp_realm eq 'admin') {
$item->update({
saltedpass => NGCP::Panel::Utils::Auth::generate_salted_hash($new_password),
});
NGCP::Panel::Utils::Admin::insert_password_journal(
$c, $item, $new_password
);
} elsif ($ngcp_realm eq 'subscriber') {
$item->update({
webpassword => $NGCP::Panel::Utils::Auth::ENCRYPT_SUBSCRIBER_WEBPASSWORDS
? NGCP::Panel::Utils::Auth::generate_salted_hash($new_password)
: $new_password,
});
NGCP::Panel::Utils::Subscriber::insert_webpassword_journal(
$c, $item, $new_password
);
}
} catch ($e) {
$self->error($c, HTTP_INTERNAL_SERVER_ERROR, "Failed to change password.", $e);
}
$c->response->status(HTTP_NO_CONTENT);
$c->response->body(q());
return $item;
}
1;
# vim: set tabstop=4 expandtab:

@ -0,0 +1,29 @@
package NGCP::Panel::Form::PasswordChangeAPI;
use HTML::FormHandler::Moose;
use Email::Valid;
use NGCP::Panel::Utils::Form;
extends 'HTML::FormHandler';
has_field 'new_password' => (
type => 'Password',
required => 1,
label => 'Password',
);
has_block 'fields' => (
tag => 'div',
class => [qw/modal-body/],
render_list => [qw/new_password/],
);
sub validate_new_password {
my ($self, $field) = @_;
my $c = $self->form->ctx;
return unless $c;
NGCP::Panel::Utils::Form::validate_password(c => $c, field => $field, utf8 => 0, password_change => 1);
}
1;
# vim: set tabstop=4 expandtab:

@ -263,7 +263,7 @@ sub validate_form {
my $in = (defined $_->input && ref $_->input eq 'HASH' && exists $_->input->{id}) ? $_->input->{id} : ($_->input // '');
$in //= '';
my $field_name = ($_->parent->$_isa('HTML::FormHandler::Field') ? $_->parent->name . '_' : '') . $_->name;
my $secure_input = $field_name =~ /^(web)?password$/ ? '*****' : $in;
my $secure_input = $field_name =~ /^(web|new_)?password$/ ? '*****' : $in;
sprintf 'field=\'%s\', input=\'%s\', errors=\'%s\'',
$field_name, $secure_input,
#for now, we dont change the error response text, even if causes sensitive data in the logs.
@ -1785,8 +1785,8 @@ sub validate_request {
}
if (! NGCP::Panel::Utils::Auth::check_max_age($c)) {
if ($c->req->method =~ /^(PUT|PATCH)$/ && $c->req->path =~ /^api\/(admins|subscribers)\//) {
$c->stash->{validate_password_change} = 1;
if ($c->req->method eq 'POST' && $c->req->path =~ /^api\/passwordchange\//) {
$c->stash->{password_change_request} = 1;
} else {
$self->error($c, HTTP_FORBIDDEN, "Password expired");
return;

@ -155,13 +155,6 @@ sub update_item {
resource => $resource,
);
if ($c->stash->{validate_password_change}) {
if (!$resource->{password} || $resource->{password} eq $old_resource->{saltedpass}) {
$self->error($c, HTTP_FORBIDDEN, "Password expired");
return;
}
}
if ($item->id == $c->user->id) {
# user cannot modify the following own permissions for security reasons
my $own_forbidden = 0;

@ -0,0 +1,24 @@
package NGCP::Panel::Role::API::PasswordChange;
use NGCP::Panel::Utils::Generic qw(:all);
use Sipwise::Base;
use parent 'NGCP::Panel::Role::API';
use boolean qw(true);
use Data::HAL qw();
use Data::HAL::Link qw();
use HTTP::Status qw(:constants);
sub _item_rs {
}
sub get_form {
my ($self, $c) = @_;
return NGCP::Panel::Form::get("NGCP::Panel::Form::PasswordChangeAPI", $c);
}
1;
# vim: set tabstop=4 expandtab:

@ -73,8 +73,6 @@ sub _item_rs {
my $item_rs;
my $type = $self->container_resource_type;
print "TYPE: $type\n";
if($type eq "domains") {
# we actually return the domain rs here, as we can easily
# go to dom_preferences from there

@ -407,13 +407,6 @@ sub update_item {
my $groupmembers = $full_resource->{groupmembers};
my $prov_subscriber = $subscriber->provisioning_voip_subscriber;
if ($c->stash->{validate_password_change}) {
if (!$resource->{webpassword}) {
$self->error($c, HTTP_FORBIDDEN, "Password expired");
return;
}
}
$self->process_form_resource($c, $item, $full_resource, $resource, $form);
if($subscriber->provisioning_voip_subscriber->is_pbx_pilot && !is_true($resource->{is_pbx_pilot})) {

@ -27,7 +27,16 @@ sub validate_password {
} elsif ($field->name eq 'webpassword') {
$is_web_password = 1;
} elsif ($field->name eq 'new_password') {
$is_web_password = 1;
if ($pass_change) {
my $ngcp_realm = $c->request->env->{NGCP_REALM} // 'admin';
if ($ngcp_realm eq 'admin') {
$is_admin_password = 1;
} else {
$is_web_password = 1;
}
} else {
$is_web_password = 1;
}
}
if ($is_sip_password) {
@ -106,17 +115,19 @@ sub validate_password {
$lp_rs = $prov_sub->last_passwords;
$check_last_passwords = 1;
}
} elsif($field->name eq "webpassword" && $pw->{web_validate}) {
} elsif ($is_web_password) {
my $user;
my $prov_sub = $c->stash->{subscriber}
? $c->stash->{subscriber}->provisioning_voip_subscriber
: undef;
if ($pass_change && !$prov_sub) {
$prov_sub = $c->user->provisioning_voip_subscriber;
if ($c->user->can('webpassword')) {
$prov_sub = $c->user;
}
}
if ($field->form->field('webusername')) {
$user = $field->form->field('webusername')->value;
} elsif($prov_sub) {
} elsif ($prov_sub) {
$user = $prov_sub->webusername;
}
if(defined $user && $pass =~ /$user/i) {

Loading…
Cancel
Save