MT#65579 support updating existing subscribers via prov templates

Reuse shared resource_from_item/update_subscriber helpers so templates can match by identifier, fill missing fields from the DB item, and update instead of only creating.

Change-Id: I5fa9be12914677c3b6ef5c4c0617cb75447b45c9
(cherry picked from commit a0bfa9377c)
mr12.5
Rene Krenn 3 weeks ago
parent 7b8be439c6
commit bcd41c92ee

@ -47,189 +47,29 @@ sub get_form {
sub resource_from_item {
my ($self, $c, $item, $form, $patch_mode) = @_;
my $pref;
my $bill_resource = { $item->get_inflated_columns };
my $prov_resource = { $item->provisioning_voip_subscriber->get_inflated_columns };
my $customer = $self->get_customer($c, $item->contract_id);
delete $prov_resource->{domain_id};
delete $prov_resource->{account_id};
my %resource = %{ merge($bill_resource, $prov_resource) };
$resource{administrative} = delete $resource{admin};
unless($customer->product->class eq 'pbxaccount') {
delete $resource{is_pbx_group};
delete $resource{is_pbx_pilot};
delete $resource{pbx_extension};
}
unless(is_true($resource{is_pbx_group})) {
delete $resource{pbx_hunt_policy};
delete $resource{cloud_pbx_hunt_policy};
delete $resource{pbx_hunt_timeout};
delete $resource{cloud_pbx_hunt_timeout};
delete $resource{pbx_hunt_cancel_mode};
delete $resource{cloud_pbx_hunt_cancel_mode};
}
delete $resource{contact_id};
if($item->contact) {
$resource{email} = $item->contact->email;
$resource{timezone} = $item->contact->timezone;
} else {
$resource{email} = undef;
$resource{timezone} = undef;
}
my $sippassword = $resource{password};
my $webpassword = $resource{webpassword};
if(!$form){
if (!$form) {
($form) = $self->get_form($c);
}
# form validation during PATCH causes
# fields to be removed from the %resource
# and then apply_patch() removes the fields
# that were not a part the PATCH ops from
# the database, therefore a copy of the resource
# is validated instead, preserving the original one
# when $patch_mode is enabled
my %validate_resource = %resource;
last unless $self->validate_form(
return NGCP::Panel::Utils::Subscriber::resource_from_item(
c => $c,
resource => $patch_mode ? \%validate_resource : \%resource,
form => $form,
run => 0,
item => $item,
patch_mode => $patch_mode,
get_customer_code => sub {
my ($cid) = @_;
return $self->get_customer($c, $cid);
},
validate_code => sub {
my ($resource) = @_;
return $self->validate_form(
c => $c,
resource => $resource,
form => $form,
run => 0,
);
},
);
$resource{_password} = $sippassword;
$resource{_webpassword} = $webpassword;
if($customer->product->class eq 'pbxaccount') {
if ($resource{administrative} == 1) {
$resource{ext_range_min} = $customer->voip_contract_preferences->search(
{
'attribute.attribute' => 'ext_range_min'
},
{
join => 'attribute',
}
)->get_column('value')->first;
$resource{ext_range_max} = $customer->voip_contract_preferences->search(
{
'attribute.attribute' => 'ext_range_max'
},
{
join => 'attribute',
}
)->get_column('value')->first;
}
$resource{pbx_group_ids} = [];
foreach my $group($item->provisioning_voip_subscriber->voip_pbx_groups->search_rs(undef,{'order_by' => 'me.id'})->all) {
push @{ $resource{pbx_group_ids} }, int($group->group->voip_subscriber->id);
}
if($item->provisioning_voip_subscriber->is_pbx_group) {
$resource{pbx_groupmember_ids} = [];
foreach my $member($item->provisioning_voip_subscriber->voip_pbx_group_members->search_rs(undef,{'order_by' => 'me.id'})->all) {
push @{ $resource{pbx_groupmember_ids} }, int($member->subscriber->voip_subscriber->id);
}
}
}
if($item->primary_number) {
$resource{primary_number}->{cc} = $item->primary_number->cc;
$resource{primary_number}->{ac} = $item->primary_number->ac;
$resource{primary_number}->{sn} = $item->primary_number->sn;
$resource{primary_number}->{number_id} = int($item->primary_number->id);
}
my @aliases = ();
if($item->voip_numbers->first) {
foreach my $n ($item->voip_numbers->search(undef,{
prefetch => 'voip_dbalias',
})->all) {
my $alias = {
cc => $n->cc,
ac => $n->ac,
sn => $n->sn,
number_id => int($n->id),
};
next if ($resource{primary_number} && $resource{primary_number}->{number_id} == $alias->{number_id});
$alias->{is_devid} = 0;
if (defined $n->voip_dbalias) {
$alias->{is_devid} = $n->voip_dbalias->is_devid;
}
$alias->{is_devid} = bool $alias->{is_devid};
push(@aliases, $alias);
}
}
$resource{alias_numbers} = \@aliases;
$pref = NGCP::Panel::Utils::Preferences::get_usr_preference_rs(
c => $c, attribute => 'display_name',
prov_subscriber => $item->provisioning_voip_subscriber);
if($pref->first && $pref->first->value) {
$resource{display_name} = $pref->first->value;
} else {
$resource{display_name} = undef;
}
$resource{id} = int($item->id);
$resource{domain} = $item->domain->domain;
# don't leak internal info to subscribers via API for those fields
# not filtered via forms
my $contract_id = int(delete $resource{contract_id});
if ($c->user->roles eq "admin" || $c->user->roles eq "reseller" ||
$c->user->roles eq "ccareadmin" || $c->user->roles eq "ccare") {
$resource{customer_id} = $contract_id;
$resource{uuid} = $item->uuid;
my $pref = NGCP::Panel::Utils::Preferences::get_usr_preference_rs(
c => $c, attribute => 'lock',
prov_subscriber => $item->provisioning_voip_subscriber);
$resource{lock} = 0;
if($pref->first and length($pref->first->value) > 0) {
#cast to Numeric accordingly to the form field type and customer note in the ticket #10313
$resource{lock} = $pref->first->value;
}else{
$resource{lock} = undef;
}
if ($c->user->show_passwords) {
foreach my $k(qw/password webpassword/) {
eval {
if (not NGCP::Panel::Utils::Auth::is_salted_hash($resource{$k})) {
$resource{'_' . $k} = NGCP::Panel::Utils::Encryption::encrypt_rsa($c,$resource{$k});
} else {
delete $resource{'_' . $k};
}
};
if ($@) {
$c->error("Failed to encrypt $k: " . $@);
delete $resource{'_' . $k};
}
}
} else {
foreach my $k(qw/password webpassword/) {
delete $resource{'_' . $k};
}
}
} else {
delete $resource{'password'} if $c->user->roles eq 'subscriber';
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}) {
delete $resource{_password};
}
if (!$c->config->{security}->{password}->{web_expose_subadmin}) {
delete $resource{_webpassword};
}
}
}
}
return \%resource;
}
sub hal_from_item {
@ -418,208 +258,19 @@ sub update_item {
return unless $self->check_write_access($c, $item->id);
my $subscriber = $item;
my $customer = $full_resource->{customer};
my $alias_numbers = $full_resource->{alias_numbers};
my $preferences = $full_resource->{preferences};
my $groups = $full_resource->{groups};
my $groupmembers = $full_resource->{groupmembers};
my $prov_subscriber = $subscriber->provisioning_voip_subscriber;
$self->process_form_resource($c, $item, $full_resource, $resource, $form);
if($subscriber->provisioning_voip_subscriber->is_pbx_pilot && !is_true($resource->{is_pbx_pilot})) {
$self->error($c, HTTP_UNPROCESSABLE_ENTITY, "Cannot revoke is_pbx_pilot status from a subscriber.");
return;
}
if($resource->{customer_id} && ( $resource->{customer_id} != $subscriber->contract->id ) ){
$self->error($c, HTTP_UNPROCESSABLE_ENTITY, "customer_id can't be changed.");
return;
}
if ($resource->{timezone} && !NGCP::Panel::Utils::DateTime::is_valid_timezone_name($resource->{timezone})) {
$self->error($c, HTTP_UNPROCESSABLE_ENTITY, "invalid timezone name.");
return;
}
if($subscriber->status ne $resource->{status}) {
if($resource->{status} eq 'locked') {
$resource->{lock} = 4;
} elsif($subscriber->status eq 'locked' && $resource->{status} eq 'active') {
$resource->{lock} ||= 0;
} elsif($resource->{status} eq 'terminated') {
try {
NGCP::Panel::Utils::Subscriber::terminate(c => $c, subscriber => $subscriber);
return $subscriber;
} catch($e) {
$self->error($c, HTTP_INTERNAL_SERVER_ERROR, "Failed to terminate subscriber",
"failed to terminate subscriber id ".$subscriber->id, $e);
return;
}
}
}
try {
NGCP::Panel::Utils::Subscriber::lock_provisoning_voip_subscriber(
c => $c,
prov_subscriber => $prov_subscriber,
level => $resource->{lock} || 0,
);
} catch($e) {
$self->error($c, HTTP_INTERNAL_SERVER_ERROR, "Failed to update subscriber lock",
"failed to lock subscriber id ".$subscriber->id." with level ".$resource->{lock}, $e);
return;
};
my ($error,$profile_set,$profile) = NGCP::Panel::Utils::Subscriber::check_profile_set_and_profile($c, $resource, $subscriber);
if ($error) {
$self->error($c, $error->{response_code}, $error->{description}, $error->{error});
return;
}
if($resource->{email} || $resource->{timezone}) {
my $contact = $subscriber->contact;
unless ($contact) {
$contact = $schema->resultset('contacts')->create({
reseller_id => $subscriber->contract->contact->reseller_id,
});
}
if(not $contact->email or ($contact->email ne $resource->{email})) {
$contact->update({
email => $resource->{email},
});
}
if(not $contact->timezone or ($contact->timezone ne $resource->{timezone})) {
$contact->update({
timezone => $resource->{timezone},
});
}
$resource->{contact_id} = $contact->id;
} elsif($subscriber->contact) {
try {
$c->log->debug("delete contact id ".$subscriber->contact->id);
$subscriber->contact->delete;
} catch($e) {
$c->log->debug("contact still in use: ".$e);
}
$resource->{contact_id} = undef; # mark for clearance
}
delete $resource->{email};
delete $resource->{timezone};
my $aliases_before = NGCP::Panel::Utils::Events::get_aliases_snapshot(
return NGCP::Panel::Utils::Subscriber::update_subscriber(
c => $c,
schema => $schema,
subscriber => $subscriber,
);
try {
NGCP::Panel::Utils::Subscriber::update_subscriber_numbers(
c => $c,
schema => $schema,
primary_number => $resource->{e164},
alias_numbers => $alias_numbers,
reseller_id => $customer->contact->reseller_id,
subscriber_id => $subscriber->id,
);
} catch(DBIx::Class::Exception $e where { /Duplicate entry '([^']+)' for key 'number_idx'/ }) {
$e =~ /Duplicate entry '([^']+)' for key 'number_idx'/;
$self->error($c, HTTP_UNPROCESSABLE_ENTITY, "Number '" . $1 . "' already exists.", "Number already exists.",
"failed to update subscriber, number " . $c->qs($1) . " already exists");
return;
} catch ($e where { /alias \d+ already exists/ }) {
$e =~ /alias (\d+) already exists/;
$self->error($c, HTTP_UNPROCESSABLE_ENTITY, "Number '" . $1 . "' already exists.", "Number already exists.",
"failed to update subscriber, alias " . $c->qs($1) . " already exists");
return;
} catch ($e where { /aliases \S+ already exist/ }) {
$e =~ /aliases (\S+) already exist/;
$self->error($c, HTTP_UNPROCESSABLE_ENTITY, "Numbers '" . $1 . "' already exist.", "Numbers already exist.",
"failed to update subscriber, aliases " . $c->qs($1) . " already exist");
return;
} catch ($e where { /more than \d+ provided aliases/ }) {
my $err_msg = "more than 10 provided aliases already exist";
$self->error($c, HTTP_UNPROCESSABLE_ENTITY, $err_msg);
return;
}
my $billing_res = {
external_id => $resource->{external_id},
status => $resource->{status},
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});
}
my $provisioning_res = {
webusername => $resource->{webusername},
is_pbx_pilot => $resource->{is_pbx_pilot} // 0,
is_pbx_group => $resource->{is_pbx_group} // 0,
modify_timestamp => NGCP::Panel::Utils::DateTime::current_local,
profile_set_id => $profile_set ? $profile_set->id : undef,
profile_id => $profile ? $profile->id : undef,
pbx_extension => $resource->{pbx_extension},
defined $resource->{administrative} ? (admin => $resource->{administrative}) : (),
};
$provisioning_res->{password} = $resource->{password} if exists $resource->{password};
$provisioning_res->{webpassword} = $resource->{webpassword} if exists $resource->{webpassword};
if(is_true($resource->{is_pbx_group})) {
$provisioning_res->{pbx_hunt_policy} = $resource->{pbx_hunt_policy};
$provisioning_res->{pbx_hunt_timeout} = $resource->{pbx_hunt_timeout};
$provisioning_res->{pbx_hunt_cancel_mode} = $resource->{pbx_hunt_cancel_mode};
NGCP::Panel::Utils::Subscriber::update_preferences(
c => $c,
prov_subscriber => $prov_subscriber,
'preferences' => {
cloud_pbx_hunt_policy => $resource->{cloud_pbx_hunt_policy} // $resource->{pbx_hunt_policy},
cloud_pbx_hunt_timeout => $resource->{cloud_pbx_hunt_timeout} // $resource->{pbx_hunt_timeout},
cloud_pbx_hunt_cancel_mode => $resource->{cloud_pbx_hunt_cancel_mode} // $resource->{pbx_hunt_cancel_mode},
}
);
}
my $old_profile = $prov_subscriber->profile_id;
$subscriber->update($billing_res);
$subscriber->discard_changes;
$prov_subscriber->update($provisioning_res);
$prov_subscriber->discard_changes;
NGCP::Panel::Utils::Events::insert_profile_events(
c => $c, schema => $schema, subscriber_id => $subscriber->id,
old => $old_profile, new => $prov_subscriber->profile_id,
%$aliases_before,
);
NGCP::Panel::Utils::Subscriber::update_preferences(
c => $c,
prov_subscriber => $prov_subscriber,
preferences => $preferences,
);
NGCP::Panel::Utils::Subscriber::manage_pbx_groups(
c => $c,
schema => $schema,
groups => $groups,
groupmembers => $groupmembers,
customer => $customer,
subscriber => $subscriber,
item => $item,
full_resource => $full_resource,
resource => $resource,
err_code => sub {
my ($code, $msg, @errors) = @_;
$self->error($c, $code, $msg, @errors);
},
);
return $subscriber;
}
sub check_write_access {

@ -29,6 +29,7 @@ use NGCP::Panel::Utils::Subscriber qw();
use NGCP::Panel::Utils::Preferences qw();
use NGCP::Panel::Utils::Kamailio qw();
use NGCP::Panel::Utils::CallForwards qw();
use NGCP::Panel::Utils::Events qw();
BEGIN {
# Temporarily override the warning handler
@ -603,7 +604,7 @@ sub provision_commit_row {
$schema,
$c->stash->{provisioning_templates}->{$template},
);
_create_subscriber(
_create_update_subscriber(
$c,
$context,
$schema,
@ -619,7 +620,7 @@ sub provision_commit_row {
if ($purge && $subscriber) {
$c->log->debug("provisioning template - terminating subscriber id " . $subscriber->id);
NGCP::Panel::Utils::Subscriber::terminate(c => $c, subscriber => $subscriber);
_create_subscriber(
_create_update_subscriber(
$c,
$context,
$schema,
@ -644,7 +645,7 @@ sub provision_commit_row {
$schema,
$c->stash->{provisioning_templates}->{$template},
);
_create_subscriber(
_create_update_subscriber(
$c,
$context,
$schema,
@ -652,7 +653,7 @@ sub provision_commit_row {
} else {
die $msg;
}
} catch($e where { /alias '([^']+)' already exists/ }) {
} catch($e where { /alias '([^']+)' already exists/ or /more than \d+ provided aliases already exist/ }) {
my ($msg, $subscriber) = _get_duplicate_subs(
$c,
$context,
@ -671,7 +672,7 @@ sub provision_commit_row {
$schema,
$c->stash->{provisioning_templates}->{$template},
);
_create_subscriber(
_create_update_subscriber(
$c,
$context,
$schema,
@ -865,15 +866,17 @@ sub _init_contract_context {
}
$context->{contract_contact} = \%contract_contact;
if (scalar @identifiers) {
delete $contract_contact{id};
my $found;
foreach my $e ($schema->resultset('contacts')->search_rs({
map { $_ => $contract_contact{$_}; } @identifiers
})->all) {
if ('terminated' ne $e->status) {
$contract_contact{id} = $e->id;
$found = 1;
last;
}
}
delete $contract_contact{id} unless $found;
} else {
delete $contract_contact{id};
}
@ -951,7 +954,7 @@ sub _init_contract_context {
$context->{contract} = \%contract;
if (scalar @identifiers) {
delete $contract{id};
my $found;
foreach my $e ($schema->resultset('contracts')->search_rs({
map { $_ => $contract{$_}; } @identifiers
})->all) {
@ -960,9 +963,11 @@ sub _init_contract_context {
$old_resource = { $e->get_inflated_columns };
$found = 1;
last;
}
}
delete $contract{id} unless $found;
} else {
delete $contract{id};
}
@ -1025,23 +1030,36 @@ sub _init_subscriber_context {
my $item;
if (scalar @identifiers) {
delete $subscriber{id};
my $found;
foreach my $e ($schema->resultset('voip_subscribers')->search_rs({
map { $_ => $subscriber{$_}; } @identifiers
},{
#join => 'domain',
})->all) {
if ('terminated' ne $e->status) {
my $from_item = NGCP::Panel::Utils::Subscriber::resource_from_item(
c => $c,
item => $e,
);
if ($from_item) {
foreach my $k (keys %$from_item) {
next if $k eq 'id';
$subscriber{$k} = $from_item->{$k} unless exists $subscriber{$k};
}
}
$subscriber{id} = $e->id;
$item = $e;
$found = 1;
last;
}
}
delete $subscriber{id} unless $found;
} else {
delete $subscriber{id};
}
$context->{subscriber}->{customer_id} //= $context->{contract}->{id};
$context->{_subscriber_item} = $item;
$context->{_cs} = NGCP::Panel::Utils::Subscriber::prepare_resource(
c => $c,
@ -1064,7 +1082,7 @@ sub _init_subscriber_context {
return $contract;
},
item => $item,
) unless $context->{subscriber}->{id};
);
$c->log->debug("provisioning template - subscriber: " . Dumper($context->{subscriber}));
}
@ -1404,57 +1422,73 @@ sub _set_fraud_preferences {
}
sub _create_subscriber {
sub _create_update_subscriber {
my ($c, $context, $schema) = @_;
if (exists $context->{subscriber} and not $context->{subscriber}->{id}) {
my $error_info = { extended => {} };
my @events_to_create = ();
my $event_context = { events_to_create => \@events_to_create };
my $subscriber = NGCP::Panel::Utils::Subscriber::create_subscriber(
c => $c,
schema => $schema,
contract => $context->{_cs}->{customer},
params => $context->{_cs}->{resource},
preferences => $context->{_cs}->{preferences},
admin_default => 0,
event_context => $event_context,
error => $error_info,
);
$context->{subscriber}->{id} = $subscriber->id;
if($context->{_cs}->{resource}->{status} eq 'locked') {
NGCP::Panel::Utils::Subscriber::lock_provisoning_voip_subscriber(
if (exists $context->{subscriber}) {
if ($context->{subscriber}->{id}) {
my $subscriber = NGCP::Panel::Utils::Subscriber::update_subscriber(
c => $c,
prov_subscriber => $subscriber->provisioning_voip_subscriber,
level => $context->{_cs}->{resource}->{lock} || 4,
schema => $schema,
item => $context->{_subscriber_item},
full_resource => $context->{_cs},
resource => $context->{_cs}->{resource},
err_code => sub {
my ($code, $msg) = @_;
die($msg);
},
);
die("failed to update subscriber") unless $subscriber;
$c->log->debug("provisioning template - subscriber id $context->{subscriber}->{id} updated");
} else {
NGCP::Panel::Utils::ProfilePackages::underrun_lock_subscriber(c => $c, subscriber => $subscriber);
my $error_info = { extended => {} };
my @events_to_create = ();
my $event_context = { events_to_create => \@events_to_create };
my $subscriber = NGCP::Panel::Utils::Subscriber::create_subscriber(
c => $c,
schema => $schema,
contract => $context->{_cs}->{customer},
params => $context->{_cs}->{resource},
preferences => $context->{_cs}->{preferences},
admin_default => 0,
event_context => $event_context,
error => $error_info,
);
$context->{subscriber}->{id} = $subscriber->id;
if($context->{_cs}->{resource}->{status} eq 'locked') {
NGCP::Panel::Utils::Subscriber::lock_provisoning_voip_subscriber(
c => $c,
prov_subscriber => $subscriber->provisioning_voip_subscriber,
level => $context->{_cs}->{resource}->{lock} || 4,
);
} else {
NGCP::Panel::Utils::ProfilePackages::underrun_lock_subscriber(c => $c, subscriber => $subscriber);
}
NGCP::Panel::Utils::Subscriber::update_subscriber_numbers(
c => $c,
schema => $schema,
alias_numbers => $context->{_cs}->{alias_numbers},
reseller_id => $context->{_cs}->{customer}->contact->reseller_id,
subscriber_id => $subscriber->id,
);
$subscriber->discard_changes; # reload row because of new number
NGCP::Panel::Utils::Subscriber::manage_pbx_groups(
c => $c,
schema => $schema,
groups => $context->{_cs}->{groups},
groupmembers => $context->{_cs}->{groupmembers},
customer => $context->{_cs}->{customer},
subscriber => $subscriber,
);
NGCP::Panel::Utils::Events::insert_deferred(
c => $c, schema => $schema,
events_to_create => \@events_to_create,
);
$c->log->debug("provisioning template - subscriber id $context->{subscriber}->{id} created");
}
NGCP::Panel::Utils::Subscriber::update_subscriber_numbers(
c => $c,
schema => $schema,
alias_numbers => $context->{_cs}->{alias_numbers},
reseller_id => $context->{_cs}->{customer}->contact->reseller_id,
subscriber_id => $subscriber->id,
);
$subscriber->discard_changes; # reload row because of new number
NGCP::Panel::Utils::Subscriber::manage_pbx_groups(
c => $c,
schema => $schema,
groups => $context->{_cs}->{groups},
groupmembers => $context->{_cs}->{groupmembers},
customer => $context->{_cs}->{customer},
subscriber => $subscriber,
);
NGCP::Panel::Utils::Events::insert_deferred(
c => $c, schema => $schema,
events_to_create => \@events_to_create,
);
$c->log->debug("provisioning template - subscriber id $context->{subscriber}->{id} created");
}
}
@ -1820,6 +1854,36 @@ sub _get_duplicate_subs {
}
$subscriber = [ values %subs ];
}
} elsif ($e =~ /more than \d+ provided aliases already exist/) {
$msg = $e;
if ($get) {
$c->log->debug("provisioning template - $msg");
my %subs = ();
my @usernames = ();
foreach my $alias (@{ $context->{_cs}->{alias_numbers} // [] }) {
my $e164 = $alias->{e164} // $alias;
push @usernames, ($e164->{cc} // '') . ($e164->{ac} // '') . ($e164->{sn} // '');
}
if (my $e164 = $context->{_cs}->{resource}->{e164}) {
push @usernames, ($e164->{cc} // '') . ($e164->{ac} // '') . ($e164->{sn} // '');
}
@usernames = grep { length $_ } @usernames;
if (scalar @usernames) {
my $own_prov_id = $context->{_subscriber_item}
? $context->{_subscriber_item}->provisioning_voip_subscriber->id
: undef;
foreach my $alias ($c->model('DB')->resultset('voip_dbaliases')->search_rs({
username => { -in => \@usernames },
(defined $own_prov_id ? (subscriber_id => { '!=' => $own_prov_id }) : ()),
}, undef)->all) {
next unless $alias->subscriber;
my $sub = $alias->subscriber->voip_subscriber;
next unless $sub;
$subs{$sub->id} = $sub unless exists $subs{$sub->id};
}
}
$subscriber = [ values %subs ];
}
} elsif ($idx and 'number_idx' eq $idx) {
$msg = "number already exists: $e";
if ($get) {

@ -25,6 +25,7 @@ use HTTP::Status qw(:constants);
use IPC::System::Simple qw/capturex/;
use File::Slurp qw/read_file/;
use NGCP::Panel::Utils::Encryption qw();
use boolean qw(true);
my %LOCK = (
0, 'none',
@ -187,6 +188,205 @@ sub get_lock_string {
return $LOCK{$level};
}
sub resource_from_item {
my %params = @_;
my ($c, $item, $patch_mode, $validate_code, $get_customer_code) = @params{qw/
c
item
patch_mode
validate_code
get_customer_code
/};
my $pref;
my $bill_resource = { $item->get_inflated_columns };
my $prov_resource = { $item->provisioning_voip_subscriber->get_inflated_columns };
my $customer = $get_customer_code
? $get_customer_code->($item->contract_id)
: $item->contract;
return unless $customer;
delete $prov_resource->{domain_id};
delete $prov_resource->{account_id};
my %resource = %{ merge($bill_resource, $prov_resource) };
$resource{administrative} = delete $resource{admin};
unless ($customer->product->class eq 'pbxaccount') {
delete $resource{is_pbx_group};
delete $resource{is_pbx_pilot};
delete $resource{pbx_extension};
}
unless (is_true($resource{is_pbx_group})) {
delete $resource{pbx_hunt_policy};
delete $resource{cloud_pbx_hunt_policy};
delete $resource{pbx_hunt_timeout};
delete $resource{cloud_pbx_hunt_timeout};
delete $resource{pbx_hunt_cancel_mode};
delete $resource{cloud_pbx_hunt_cancel_mode};
}
delete $resource{contact_id};
if ($item->contact) {
$resource{email} = $item->contact->email;
$resource{timezone} = $item->contact->timezone;
} else {
$resource{email} = undef;
$resource{timezone} = undef;
}
my $sippassword = $resource{password};
my $webpassword = $resource{webpassword};
if ($validate_code) {
# form validation during PATCH causes fields to be removed from the
# %resource and then apply_patch() removes the fields that were not a
# part the PATCH ops from the database, therefore a copy of the resource
# is validated instead, preserving the original one when $patch_mode is
# enabled
my %validate_resource = %resource;
return unless $validate_code->($patch_mode ? \%validate_resource : \%resource);
}
$resource{_password} = $sippassword;
$resource{_webpassword} = $webpassword;
if ($customer->product->class eq 'pbxaccount') {
if ($resource{administrative} == 1) {
$resource{ext_range_min} = $customer->voip_contract_preferences->search(
{
'attribute.attribute' => 'ext_range_min'
},
{
join => 'attribute',
}
)->get_column('value')->first;
$resource{ext_range_max} = $customer->voip_contract_preferences->search(
{
'attribute.attribute' => 'ext_range_max'
},
{
join => 'attribute',
}
)->get_column('value')->first;
}
$resource{pbx_group_ids} = [];
foreach my $group ($item->provisioning_voip_subscriber->voip_pbx_groups->search_rs(undef, { 'order_by' => 'me.id' })->all) {
push @{ $resource{pbx_group_ids} }, int($group->group->voip_subscriber->id);
}
if ($item->provisioning_voip_subscriber->is_pbx_group) {
$resource{pbx_groupmember_ids} = [];
foreach my $member ($item->provisioning_voip_subscriber->voip_pbx_group_members->search_rs(undef, { 'order_by' => 'me.id' })->all) {
push @{ $resource{pbx_groupmember_ids} }, int($member->subscriber->voip_subscriber->id);
}
}
}
if ($item->primary_number) {
$resource{primary_number}->{cc} = $item->primary_number->cc;
$resource{primary_number}->{ac} = $item->primary_number->ac;
$resource{primary_number}->{sn} = $item->primary_number->sn;
$resource{primary_number}->{number_id} = int($item->primary_number->id);
} else {
$resource{primary_number} = undef;
}
my @aliases = ();
if ($item->voip_numbers->first) {
foreach my $n ($item->voip_numbers->search(undef, {
prefetch => 'voip_dbalias',
})->all) {
my $alias = {
cc => $n->cc,
ac => $n->ac,
sn => $n->sn,
number_id => int($n->id),
};
next if ($resource{primary_number} && $resource{primary_number}->{number_id} == $alias->{number_id});
$alias->{is_devid} = 0;
if (defined $n->voip_dbalias) {
$alias->{is_devid} = $n->voip_dbalias->is_devid;
}
$alias->{is_devid} = bool $alias->{is_devid};
push(@aliases, $alias);
}
}
$resource{alias_numbers} = \@aliases;
$pref = NGCP::Panel::Utils::Preferences::get_usr_preference_rs(
c => $c, attribute => 'display_name',
prov_subscriber => $item->provisioning_voip_subscriber);
if ($pref && $pref->first && $pref->first->value) {
$resource{display_name} = $pref->first->value;
} else {
$resource{display_name} = undef;
}
$resource{id} = int($item->id);
$resource{domain} = $item->domain->domain;
# don't leak internal info to subscribers via API for those fields
# not filtered via forms
my $contract_id = int(delete $resource{contract_id});
# ngcp-provisioning-template Catalyst stub has no user(); presume admin.
my $user = ($c->can('user') ? $c->user : undef);
my $roles = $user ? $user->roles : 'admin';
my $show_passwords = ($user && $user->can('show_passwords')) ? $user->show_passwords : 0;
if ($roles eq "admin" || $roles eq "reseller" ||
$roles eq "ccareadmin" || $roles eq "ccare") {
$resource{customer_id} = $contract_id;
$resource{uuid} = $item->uuid;
my $lock_pref = NGCP::Panel::Utils::Preferences::get_usr_preference_rs(
c => $c, attribute => 'lock',
prov_subscriber => $item->provisioning_voip_subscriber);
$resource{lock} = 0;
if ($lock_pref && $lock_pref->first && length($lock_pref->first->value) > 0) {
#cast to Numeric accordingly to the form field type and customer note in the ticket #10313
$resource{lock} = $lock_pref->first->value;
} else {
$resource{lock} = undef;
}
if ($show_passwords) {
foreach my $k (qw/password webpassword/) {
eval {
if (not NGCP::Panel::Utils::Auth::is_salted_hash($resource{$k})) {
$resource{'_' . $k} = NGCP::Panel::Utils::Encryption::encrypt_rsa($c, $resource{$k});
} else {
delete $resource{'_' . $k};
}
};
if ($@) {
$c->log->error("Failed to encrypt $k: " . $@) if $c->can('log');
delete $resource{'_' . $k};
}
}
} else {
foreach my $k (qw/password webpassword/) {
delete $resource{'_' . $k};
}
}
} else {
delete $resource{'password'} if $roles eq 'subscriber';
if ($roles eq "subscriberadmin") {
$resource{customer_id} = $contract_id;
if ($user && $item->id != $user->voip_subscriber->id) {
if (!$c->config->{security}->{password}->{sip_expose_subadmin}) {
delete $resource{_password};
}
if (!$c->config->{security}->{password}->{web_expose_subadmin}) {
delete $resource{_webpassword};
}
}
}
}
return \%resource;
}
sub prepare_resource {
my %params = @_;
@ -901,6 +1101,229 @@ sub create_subscriber {
});
}
sub update_subscriber {
my %params = @_;
my ($c, $schema, $item, $full_resource, $resource, $err_code) = @params{qw/
c
schema
item
full_resource
resource
err_code
/};
if (!defined $err_code || ref $err_code ne 'CODE') {
$err_code = sub { };
}
$schema //= $c->model('DB');
$resource //= $full_resource->{resource};
my $subscriber = $item;
my $customer = $full_resource->{customer};
my $alias_numbers = $full_resource->{alias_numbers};
my $preferences = $full_resource->{preferences};
my $groups = $full_resource->{groups};
my $groupmembers = $full_resource->{groupmembers};
my $prov_subscriber = $subscriber->provisioning_voip_subscriber;
$resource->{timezone} = $resource->{timezone}->{name} if 'HASH' eq ref $resource->{timezone};
$resource->{timezone} = NGCP::Panel::Utils::DateTime::get_timezone_link($c, $resource->{timezone});
if ($subscriber->provisioning_voip_subscriber->is_pbx_pilot && !is_true($resource->{is_pbx_pilot})) {
&$err_code(HTTP_UNPROCESSABLE_ENTITY, "Cannot revoke is_pbx_pilot status from a subscriber.");
return;
}
if ($resource->{customer_id} && ($resource->{customer_id} != $subscriber->contract->id)) {
&$err_code(HTTP_UNPROCESSABLE_ENTITY, "customer_id can't be changed.");
return;
}
if ($resource->{timezone} && !NGCP::Panel::Utils::DateTime::is_valid_timezone_name($resource->{timezone})) {
&$err_code(HTTP_UNPROCESSABLE_ENTITY, "invalid timezone name.");
return;
}
if ($subscriber->status ne $resource->{status}) {
if ($resource->{status} eq 'locked') {
$resource->{lock} = 4;
} elsif ($subscriber->status eq 'locked' && $resource->{status} eq 'active') {
$resource->{lock} ||= 0;
} elsif ($resource->{status} eq 'terminated') {
try {
terminate(c => $c, subscriber => $subscriber);
return $subscriber;
} catch ($e) {
&$err_code(HTTP_INTERNAL_SERVER_ERROR, "Failed to terminate subscriber",
"failed to terminate subscriber id ".$subscriber->id, $e);
return;
}
}
}
try {
lock_provisoning_voip_subscriber(
c => $c,
prov_subscriber => $prov_subscriber,
level => $resource->{lock} || 0,
);
} catch ($e) {
&$err_code(HTTP_INTERNAL_SERVER_ERROR, "Failed to update subscriber lock",
"failed to lock subscriber id ".$subscriber->id." with level ".$resource->{lock}, $e);
return;
}
my ($error, $profile_set, $profile) = check_profile_set_and_profile($c, $resource, $subscriber);
if ($error) {
&$err_code($error->{response_code}, $error->{description}, $error->{error});
return;
}
if ($resource->{email} || $resource->{timezone}) {
my $contact = $subscriber->contact;
unless ($contact) {
$contact = $schema->resultset('contacts')->create({
reseller_id => $subscriber->contract->contact->reseller_id,
});
}
if (not $contact->email or ($contact->email ne $resource->{email})) {
$contact->update({
email => $resource->{email},
});
}
if (not $contact->timezone or ($contact->timezone ne $resource->{timezone})) {
$contact->update({
timezone => $resource->{timezone},
});
}
$resource->{contact_id} = $contact->id;
} elsif ($subscriber->contact) {
try {
$c->log->debug("delete contact id ".$subscriber->contact->id);
$subscriber->contact->delete;
} catch ($e) {
$c->log->debug("contact still in use: ".$e);
}
$resource->{contact_id} = undef; # mark for clearance
}
delete $resource->{email};
delete $resource->{timezone};
my $aliases_before = NGCP::Panel::Utils::Events::get_aliases_snapshot(
c => $c,
schema => $schema,
subscriber => $subscriber,
);
try {
update_subscriber_numbers(
c => $c,
schema => $schema,
primary_number => $resource->{e164},
alias_numbers => $alias_numbers,
reseller_id => $customer->contact->reseller_id,
subscriber_id => $subscriber->id,
);
} catch (DBIx::Class::Exception $e where { /Duplicate entry '([^']+)' for key 'number_idx'/ }) {
$e =~ /Duplicate entry '([^']+)' for key 'number_idx'/;
&$err_code(HTTP_UNPROCESSABLE_ENTITY, "Number '" . $1 . "' already exists.", "Number already exists.",
"failed to update subscriber, number " . $1 . " already exists");
return;
} catch ($e where { /alias \d+ already exists/ }) {
$e =~ /alias (\d+) already exists/;
&$err_code(HTTP_UNPROCESSABLE_ENTITY, "Number '" . $1 . "' already exists.", "Number already exists.",
"failed to update subscriber, alias " . $1 . " already exists");
return;
} catch ($e where { /aliases \S+ already exist/ }) {
$e =~ /aliases (\S+) already exist/;
&$err_code(HTTP_UNPROCESSABLE_ENTITY, "Numbers '" . $1 . "' already exist.", "Numbers already exist.",
"failed to update subscriber, aliases " . $1 . " already exist");
return;
} catch ($e where { /more than \d+ provided aliases/ }) {
&$err_code(HTTP_UNPROCESSABLE_ENTITY, "more than 10 provided aliases already exist");
return;
}
my $billing_res = {
external_id => $resource->{external_id},
status => $resource->{status},
contact_id => $resource->{contact_id},
};
if ($resource->{password} && $resource->{password} ne $prov_subscriber->password) {
insert_password_journal(
$c, $prov_subscriber, $resource->{password}
);
}
if ($resource->{webpassword}) {
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});
}
my $provisioning_res = {
webusername => $resource->{webusername},
is_pbx_pilot => $resource->{is_pbx_pilot} // 0,
is_pbx_group => $resource->{is_pbx_group} // 0,
modify_timestamp => NGCP::Panel::Utils::DateTime::current_local,
profile_set_id => $profile_set ? $profile_set->id : undef,
profile_id => $profile ? $profile->id : undef,
pbx_extension => $resource->{pbx_extension},
defined $resource->{administrative} ? (admin => $resource->{administrative}) : (),
};
$provisioning_res->{password} = $resource->{password} if exists $resource->{password};
$provisioning_res->{webpassword} = $resource->{webpassword} if exists $resource->{webpassword};
if (is_true($resource->{is_pbx_group})) {
$provisioning_res->{pbx_hunt_policy} = $resource->{pbx_hunt_policy};
$provisioning_res->{pbx_hunt_timeout} = $resource->{pbx_hunt_timeout};
$provisioning_res->{pbx_hunt_cancel_mode} = $resource->{pbx_hunt_cancel_mode};
update_preferences(
c => $c,
prov_subscriber => $prov_subscriber,
'preferences' => {
cloud_pbx_hunt_policy => $resource->{cloud_pbx_hunt_policy} // $resource->{pbx_hunt_policy},
cloud_pbx_hunt_timeout => $resource->{cloud_pbx_hunt_timeout} // $resource->{pbx_hunt_timeout},
cloud_pbx_hunt_cancel_mode => $resource->{cloud_pbx_hunt_cancel_mode} // $resource->{pbx_hunt_cancel_mode},
}
);
}
my $old_profile = $prov_subscriber->profile_id;
$subscriber->update($billing_res);
$subscriber->discard_changes;
$prov_subscriber->update($provisioning_res);
$prov_subscriber->discard_changes;
NGCP::Panel::Utils::Events::insert_profile_events(
c => $c, schema => $schema, subscriber_id => $subscriber->id,
old => $old_profile, new => $prov_subscriber->profile_id,
%$aliases_before,
);
update_preferences(
c => $c,
prov_subscriber => $prov_subscriber,
preferences => $preferences,
);
manage_pbx_groups(
c => $c,
schema => $schema,
groups => $groups,
groupmembers => $groupmembers,
customer => $customer,
subscriber => $subscriber,
);
return $subscriber;
}
sub check_profile_set_and_profile {
my ($c, $resource, $subscriber) = @_;

Loading…
Cancel
Save