diff --git a/lib/NGCP/Panel/Controller/API/CFDestinationSets.pm b/lib/NGCP/Panel/Controller/API/CFDestinationSets.pm index 5aaa3e5d7c..f94d2d19a2 100644 --- a/lib/NGCP/Panel/Controller/API/CFDestinationSets.pm +++ b/lib/NGCP/Panel/Controller/API/CFDestinationSets.pm @@ -182,11 +182,10 @@ sub POST :Allow { my $dset; - if($c->user->roles eq "subscriberadmin" || $c->user->roles eq "subscriber") { + if($c->user->roles eq "subscriberadmin") { + $resource->{subscriber_id} //= $c->user->voip_subscriber->id; + } elsif($c->user->roles eq "subscriber") { $resource->{subscriber_id} = $c->user->voip_subscriber->id; - } elsif(!defined $resource->{subscriber_id}) { - $self->error($c, HTTP_UNPROCESSABLE_ENTITY, "Missing mandatory field 'subscriber_id'"); - last; } my $b_subscriber = $schema->resultset('voip_subscribers')->find({ @@ -196,6 +195,12 @@ sub POST :Allow { $self->error($c, HTTP_UNPROCESSABLE_ENTITY, "Invalid 'subscriber_id'."); last; } + #if($c->user->roles eq "subscriberadmin" && + # $b_subscriber->contract_id != $c->user->account_id) { + # $self->error($c, HTTP_UNPROCESSABLE_ENTITY, "Invalid subscriber."); + # last; + #} + my $subscriber = $b_subscriber->provisioning_voip_subscriber; unless($subscriber) { $self->error($c, HTTP_UNPROCESSABLE_ENTITY, "Invalid subscriber."); diff --git a/lib/NGCP/Panel/Controller/API/CFTimeSetsItem.pm b/lib/NGCP/Panel/Controller/API/CFTimeSetsItem.pm index 3eb5b1db4e..8e7190d71f 100644 --- a/lib/NGCP/Panel/Controller/API/CFTimeSetsItem.pm +++ b/lib/NGCP/Panel/Controller/API/CFTimeSetsItem.pm @@ -42,7 +42,7 @@ __PACKAGE__->config( action => { (map { $_ => { ACLDetachTo => '/api/root/invalid_user', - AllowedRole => [qw/admin reseller subscriberadmin subscribere/], + AllowedRole => [qw/admin reseller subscriberadmin subscriber/], Args => 1, Does => [qw(ACL RequireSSL)], Method => $_, diff --git a/lib/NGCP/Panel/Controller/Root.pm b/lib/NGCP/Panel/Controller/Root.pm index 342224f097..7b07ea3288 100644 --- a/lib/NGCP/Panel/Controller/Root.pm +++ b/lib/NGCP/Panel/Controller/Root.pm @@ -148,17 +148,17 @@ sub auto :Private { $c->log->debug("++++++ checking '".$c->user->domain->domain."' against '$d'"); if ($c->user->domain->domain ne $d) { $c->user->logout; - $c->log->debug("+++++ invalid api subscriber http login (domain check failed)"); - $c->log->warn("invalid api http login from '".$c->req->address."'"); + $c->log->debug("+++++ invalid api subscriber http login by '$username' (domain check failed)"); + $c->log->warn("invalid api http login from '".$c->req->address."' by '$username'"); my $r = $c->get_auth_realm($realm); $r->credential->authorization_required_response($c, $r); return; } - $c->log->debug("++++++ subscriber '".$c->user->webusername."' authenticated via api_subscriber_http"); + $c->log->debug("++++++ subscriber '$username' authenticated via api_subscriber_http"); } else { $c->user->logout if($c->user); $c->log->debug("+++++ invalid api subscriber http login"); - $c->log->warn("invalid api http login from '".$c->req->address."'"); + $c->log->warn("invalid api http login from '".$c->req->address."' by '$username'"); my $r = $c->get_auth_realm($realm); $r->credential->authorization_required_response($c, $r); return; @@ -173,7 +173,7 @@ sub auto :Private { unless($c->user_exists && $c->user->is_active) { $c->user->logout if($c->user); $c->log->debug("+++++ invalid api admin http login"); - $c->log->warn("invalid api http login from '".$c->req->address."'"); + $c->log->warn("invalid api http login from '".$c->req->address."' by '$user'"); my $r = $c->get_auth_realm($realm); $r->credential->authorization_required_response($c, $r); return; diff --git a/lib/NGCP/Panel/Form/CallForward/CFDestinationSetAPI.pm b/lib/NGCP/Panel/Form/CallForward/CFDestinationSetAPI.pm new file mode 100644 index 0000000000..10d8977356 --- /dev/null +++ b/lib/NGCP/Panel/Form/CallForward/CFDestinationSetAPI.pm @@ -0,0 +1,16 @@ +package NGCP::Panel::Form::CallForward::CFDestinationSetAPI; +use HTML::FormHandler::Moose; +extends 'NGCP::Panel::Form::CallForward::CFDestinationSetSubAPI'; + +has_field 'subscriber_id' => ( + type => 'PosInteger', + required => 1, + element_attr => { + rel => ['tooltip'], + title => ['The subscriber id this destination set belongs to.'] + }, +); + +1; + +# vim: set tabstop=4 expandtab: diff --git a/lib/NGCP/Panel/Form/CFDestinationSetAPI.pm b/lib/NGCP/Panel/Form/CallForward/CFDestinationSetSubAPI.pm similarity index 87% rename from lib/NGCP/Panel/Form/CFDestinationSetAPI.pm rename to lib/NGCP/Panel/Form/CallForward/CFDestinationSetSubAPI.pm index dd408324da..97aafc2ffd 100644 --- a/lib/NGCP/Panel/Form/CFDestinationSetAPI.pm +++ b/lib/NGCP/Panel/Form/CallForward/CFDestinationSetSubAPI.pm @@ -1,21 +1,11 @@ -package NGCP::Panel::Form::CFDestinationSetAPI; +package NGCP::Panel::Form::CallForward::CFDestinationSetSubAPI; use HTML::FormHandler::Moose; -use HTML::FormHandler::Widget::Block::Bootstrap; extends 'HTML::FormHandler'; has_field 'id' => ( type => 'Hidden', ); -has_field 'subscriber_id' => ( - type => 'PosInteger', - required => 1, - element_attr => { - rel => ['tooltip'], - title => ['The subscriber id this destination set belongs to.'] - }, -); - has_field 'name' => ( type => 'Text', label => 'Name', diff --git a/lib/NGCP/Panel/Form/CallForward/CFDestinationSetSubadminAPI.pm b/lib/NGCP/Panel/Form/CallForward/CFDestinationSetSubadminAPI.pm new file mode 100644 index 0000000000..1565f91644 --- /dev/null +++ b/lib/NGCP/Panel/Form/CallForward/CFDestinationSetSubadminAPI.pm @@ -0,0 +1,16 @@ +package NGCP::Panel::Form::CallForward::CFDestinationSetSubadminAPI; +use HTML::FormHandler::Moose; +extends 'NGCP::Panel::Form::CallForward::CFDestinationSetSubAPI'; + +has_field 'subscriber_id' => ( + type => 'PosInteger', + required => 0, + element_attr => { + rel => ['tooltip'], + title => ['The subscriber id this destination set belongs to, or null to set it for own subscriber.'] + }, +); + +1; + +# vim: set tabstop=4 expandtab: diff --git a/lib/NGCP/Panel/Form/CFMappingsAPI.pm b/lib/NGCP/Panel/Form/CallForward/CFMappingsAPI.pm similarity index 72% rename from lib/NGCP/Panel/Form/CFMappingsAPI.pm rename to lib/NGCP/Panel/Form/CallForward/CFMappingsAPI.pm index 47243f374c..c82934168d 100644 --- a/lib/NGCP/Panel/Form/CFMappingsAPI.pm +++ b/lib/NGCP/Panel/Form/CallForward/CFMappingsAPI.pm @@ -1,12 +1,7 @@ -package NGCP::Panel::Form::CFMappingsAPI; +package NGCP::Panel::Form::CallForward::CFMappingsAPI; use HTML::FormHandler::Moose; -use HTML::FormHandler::Widget::Block::Bootstrap; extends 'HTML::FormHandler'; -has '+widget_wrapper' => (default => 'Bootstrap'); -sub build_render_list {[qw/fields actions/]} -sub build_form_element_class {[qw(form-horizontal)]} - has_field 'id' => ( type => 'Hidden', noupdate => 1, @@ -73,7 +68,8 @@ has_field 'cfs' => ( rel => ['tooltip'], title => ['Call Forward SMS, Number of Objects, each containing the keys ' . '"destinationset", "timeset" and "sourceset". The values must be the name of ' . - 'a corresponding set which belongs to the same subscriber.'], + 'a corresponding set which belongs to the same subscriber. Alternatively, ' . + 'you can pass destinationset_id, timeset_id and sourceset_id instead of names.'], }, ); @@ -83,100 +79,169 @@ has_field 'cfu.destinationset' => ( do_label => 0, ); +has_field 'cfu.destinationset_id' => ( + type => 'PosInteger', + do_label => 0, +); + has_field 'cfu.timeset' => ( type => 'Text', do_wrapper => 1, do_label => 0, ); +has_field 'cfu.timeset_id' => ( + type => 'PosInteger', + do_label => 0, +); + has_field 'cfu.sourceset' => ( type => 'Text', do_wrapper => 1, do_label => 0, ); +has_field 'cfu.sourceset_id' => ( + type => 'PosInteger', + do_label => 0, +); + has_field 'cfb.destinationset' => ( type => 'Text', do_wrapper => 1, do_label => 0, ); +has_field 'cfb.destinationset_id' => ( + type => 'PosInteger', + do_label => 0, +); + has_field 'cfb.timeset' => ( type => 'Text', do_wrapper => 1, do_label => 0, ); +has_field 'cfb.timeset_id' => ( + type => 'PosInteger', + do_label => 0, +); + has_field 'cfb.sourceset' => ( type => 'Text', do_wrapper => 1, do_label => 0, ); +has_field 'cfb.sourceset_id' => ( + type => 'PosInteger', + do_label => 0, +); + has_field 'cft.destinationset' => ( type => 'Text', do_wrapper => 1, do_label => 0, ); +has_field 'cft.destinationset_id' => ( + type => 'PosInteger', + do_label => 0, +); + has_field 'cft.timeset' => ( type => 'Text', do_wrapper => 1, do_label => 0, ); +has_field 'cft.timeset_id' => ( + type => 'PosInteger', + do_label => 0, +); + has_field 'cft.sourceset' => ( type => 'Text', do_wrapper => 1, do_label => 0, ); +has_field 'cft.sourceset_id' => ( + type => 'PosInteger', + do_label => 0, +); + has_field 'cfna.destinationset' => ( type => 'Text', do_wrapper => 1, do_label => 0, ); +has_field 'cfna.destinationset_id' => ( + type => 'PosInteger', + do_label => 0, +); + has_field 'cfna.timeset' => ( type => 'Text', do_wrapper => 1, do_label => 0, ); +has_field 'cfna.timeset_id' => ( + type => 'PosInteger', + do_label => 0, +); + has_field 'cfna.sourceset' => ( type => 'Text', do_wrapper => 1, do_label => 0, ); +has_field 'cfna.sourceset_id' => ( + type => 'PosInteger', + do_label => 0, +); + has_field 'cfs.destinationset' => ( type => 'Text', do_wrapper => 1, do_label => 0, ); +has_field 'cfs.destinationset_id' => ( + type => 'PosInteger', + do_label => 0, +); + has_field 'cfs.timeset' => ( type => 'Text', do_wrapper => 1, do_label => 0, ); +has_field 'cfs.timeset_id' => ( + type => 'PosInteger', + do_label => 0, +); + has_field 'cfs.sourceset' => ( type => 'Text', do_wrapper => 1, do_label => 0, ); -has_field 'cft_ringtimeout' => ( +has_field 'cfs.sourceset_id' => ( type => 'PosInteger', - do_wrapper => 1, do_label => 0, ); -has_block 'fields' => ( - tag => 'div', - class => [qw(modal-body)], - render_list => [qw(cfu cfb cft cfna cfs)], +has_field 'cft_ringtimeout' => ( + type => 'PosInteger', + do_wrapper => 1, + do_label => 0, ); 1; diff --git a/lib/NGCP/Panel/Form/CallForward/CFSourceSetAPI.pm b/lib/NGCP/Panel/Form/CallForward/CFSourceSetAPI.pm new file mode 100644 index 0000000000..dde09f50bc --- /dev/null +++ b/lib/NGCP/Panel/Form/CallForward/CFSourceSetAPI.pm @@ -0,0 +1,16 @@ +package NGCP::Panel::Form::CallForward::CFSourceSetAPI; +use HTML::FormHandler::Moose; +extends 'NGCP::Panel::Form::CallForward::CFSourceSetSubAPI'; + +has_field 'subscriber_id' => ( + type => 'PosInteger', + required => 1, + element_attr => { + rel => ['tooltip'], + title => ['The subscriber id this source set belongs to'] + }, +); + +1; + +# vim: set tabstop=4 expandtab: diff --git a/lib/NGCP/Panel/Form/CFSourceSetAPI.pm b/lib/NGCP/Panel/Form/CallForward/CFSourceSetSubAPI.pm similarity index 71% rename from lib/NGCP/Panel/Form/CFSourceSetAPI.pm rename to lib/NGCP/Panel/Form/CallForward/CFSourceSetSubAPI.pm index 03390658f1..e4675bb3fd 100644 --- a/lib/NGCP/Panel/Form/CFSourceSetAPI.pm +++ b/lib/NGCP/Panel/Form/CallForward/CFSourceSetSubAPI.pm @@ -1,25 +1,13 @@ -package NGCP::Panel::Form::CFSourceSetAPI; +package NGCP::Panel::Form::CallForward::CFSourceSetSubAPI; use HTML::FormHandler::Moose; -use HTML::FormHandler::Widget::Block::Bootstrap; extends 'HTML::FormHandler'; has_field 'id' => ( type => 'Hidden', ); -has_field 'subscriber_id' => ( - type => 'PosInteger', - required => 1, - element_attr => { - rel => ['tooltip'], - title => ['The subscriber id this source set belongs to'] - }, -); - has_field 'name' => ( type => 'Text', - label => 'Name', - wrapper_class => [qw/hfh-rep-field/], required => 1, element_attr => { rel => ['tooltip'], @@ -36,7 +24,7 @@ has_field 'mode' => ( required => 1, element_attr => { rel => ['tooltip'], - title => ['The source set mode'] + title => ['The source set mode. A blacklist forwards everything except numbers in the list, a whitelist only forwards numbers in this list.'] }, ); @@ -45,7 +33,7 @@ has_field 'sources' => ( element_attr => { rel => ['tooltip'], title => ['An array of sources, each containing the key "source" ' . - 'which will be matched against the call\'s anumber to determine ' . + 'which will be matched against the calling party number to determine ' . 'whether to apply the callforward or not. ' . '"source" is the calling party number in E164 format to match. ' . 'Shell patterns like 431* or 49123[1-5]67 are possible. ' . diff --git a/lib/NGCP/Panel/Form/CallForward/CFSourceSetSubadminAPI.pm b/lib/NGCP/Panel/Form/CallForward/CFSourceSetSubadminAPI.pm new file mode 100644 index 0000000000..27368dd0e2 --- /dev/null +++ b/lib/NGCP/Panel/Form/CallForward/CFSourceSetSubadminAPI.pm @@ -0,0 +1,16 @@ +package NGCP::Panel::Form::CallForward::CFSourceSetSubadminAPI; +use HTML::FormHandler::Moose; +extends 'NGCP::Panel::Form::CallForward::CFSourceSetSubAPI'; + +has_field 'subscriber_id' => ( + type => 'PosInteger', + required => 0, + element_attr => { + rel => ['tooltip'], + title => ['The subscriber id this source set belongs to. Defaults to own subscriber id if not given.'] + }, +); + +1; + +# vim: set tabstop=4 expandtab: diff --git a/lib/NGCP/Panel/Form/CallForward/CFTimeSetAPI.pm b/lib/NGCP/Panel/Form/CallForward/CFTimeSetAPI.pm new file mode 100644 index 0000000000..4f12fb4d8e --- /dev/null +++ b/lib/NGCP/Panel/Form/CallForward/CFTimeSetAPI.pm @@ -0,0 +1,16 @@ +package NGCP::Panel::Form::CallForward::CFTimeSetAPI; +use HTML::FormHandler::Moose; +extends 'NGCP::Panel::Form::CallForward::CFTimeSetSubAPI'; + +has_field 'subscriber_id' => ( + type => 'PosInteger', + required => 1, + element_attr => { + rel => ['tooltip'], + title => ['The subscriber id this time set belongs to.'] + }, +); + +1; + +# vim: set tabstop=4 expandtab: diff --git a/lib/NGCP/Panel/Form/CFTimeSetAPI.pm b/lib/NGCP/Panel/Form/CallForward/CFTimeSetSubAPI.pm similarity index 84% rename from lib/NGCP/Panel/Form/CFTimeSetAPI.pm rename to lib/NGCP/Panel/Form/CallForward/CFTimeSetSubAPI.pm index aa381cfaa0..aa0db0cee4 100644 --- a/lib/NGCP/Panel/Form/CFTimeSetAPI.pm +++ b/lib/NGCP/Panel/Form/CallForward/CFTimeSetSubAPI.pm @@ -1,6 +1,5 @@ -package NGCP::Panel::Form::CFTimeSetAPI; +package NGCP::Panel::Form::CallForward::CFTimeSetSubAPI; use HTML::FormHandler::Moose; -use HTML::FormHandler::Widget::Block::Bootstrap; extends 'HTML::FormHandler'; has_field 'id' => ( @@ -13,15 +12,6 @@ has_field 'name' => ( required => 1, ); -has_field 'subscriber_id' => ( - type => 'PosInteger', - required => 1, - element_attr => { - rel => ['tooltip'], - title => ['The subscriber id this time set belongs to.'] - }, -); - has_field 'times' => ( type => 'Repeatable', do_wrapper => 1, diff --git a/lib/NGCP/Panel/Role/API/CFDestinationSets.pm b/lib/NGCP/Panel/Role/API/CFDestinationSets.pm index 6abc70f9e4..8f99a23211 100644 --- a/lib/NGCP/Panel/Role/API/CFDestinationSets.pm +++ b/lib/NGCP/Panel/Role/API/CFDestinationSets.pm @@ -12,11 +12,21 @@ use NGCP::Panel::Utils::DataHalLink qw(); use HTTP::Status qw(:constants); use JSON::Types; use NGCP::Panel::Utils::Subscriber; -use NGCP::Panel::Form::CFDestinationSetAPI; +use NGCP::Panel::Form::CallForward::CFDestinationSetAPI; +use NGCP::Panel::Form::CallForward::CFDestinationSetSubadminAPI; +use NGCP::Panel::Form::CallForward::CFDestinationSetSubAPI; sub get_form { my ($self, $c) = @_; - return NGCP::Panel::Form::CFDestinationSetAPI->new; + if($c->user->roles eq "subscriber") { + return NGCP::Panel::Form::CallForward::CFDestinationSetSubAPI->new; + } elsif($c->user->roles eq "subscriberadmin") { + # TODO: allow subadmin to manipulate other subs? + #return NGCP::Panel::Form::CallForward::CFDestinationSetSubadminAPI->new; + return NGCP::Panel::Form::CallForward::CFDestinationSetSubAPI->new; + } else { + return NGCP::Panel::Form::CallForward::CFDestinationSetAPI->new; + } } sub hal_from_item { @@ -43,6 +53,7 @@ sub hal_from_item { my $b_subs_id = $item->subscriber->voip_subscriber->id; $resource{subscriber_id} = $b_subs_id; + my $adm = $c->user->roles eq "admin" || $c->user->roles eq "reseller"; my $hal = NGCP::Panel::Utils::DataHal->new( links => [ @@ -55,9 +66,8 @@ sub hal_from_item { NGCP::Panel::Utils::DataHalLink->new(relation => 'collection', href => sprintf("%s", $self->dispatch_path)), NGCP::Panel::Utils::DataHalLink->new(relation => 'profile', href => 'http://purl.org/sipwise/ngcp-api/'), NGCP::Panel::Utils::DataHalLink->new(relation => 'self', href => sprintf("%s%d", $self->dispatch_path, $item->id)), - NGCP::Panel::Utils::DataHalLink->new(relation => "ngcp:$type", href => sprintf("/api/%s/%d", $type, $item->id)), NGCP::Panel::Utils::DataHalLink->new(relation => "ngcp:subscribers", href => sprintf("/api/subscribers/%d", $b_subs_id)), - $self->get_journal_relation_link($item->id), + $adm ? $self->get_journal_relation_link($item->id) : (), ], relation => 'ngcp:'.$self->resource_name, ); @@ -117,6 +127,10 @@ sub update_item { resource => $resource, exceptions => [ "subscriber_id" ], ); + if($c->user->roles eq "subscriberadmin" || $c->user->roles eq "subscriber") { + $resource->{subscriber_id} = $c->user->voip_subscriber->id; + } + if (! exists $resource->{destinations} ) { $resource->{destinations} = []; diff --git a/lib/NGCP/Panel/Role/API/CFMappings.pm b/lib/NGCP/Panel/Role/API/CFMappings.pm index 54f42149c6..9bdd5f4cd6 100644 --- a/lib/NGCP/Panel/Role/API/CFMappings.pm +++ b/lib/NGCP/Panel/Role/API/CFMappings.pm @@ -13,11 +13,11 @@ use HTTP::Status qw(:constants); use JSON::Types; use NGCP::Panel::Utils::Subscriber; use NGCP::Panel::Utils::Preferences; -use NGCP::Panel::Form::CFMappingsAPI; +use NGCP::Panel::Form::CallForward::CFMappingsAPI; sub get_form { my ($self, $c) = @_; - return NGCP::Panel::Form::CFMappingsAPI->new; + return NGCP::Panel::Form::CallForward::CFMappingsAPI->new; } sub hal_from_item { @@ -33,16 +33,33 @@ sub hal_from_item { $ringtimeout_preference = $ringtimeout_preference ? $ringtimeout_preference->value : undef; for my $mapping ($item->provisioning_voip_subscriber->voip_cf_mappings->all) { - my $dset = $mapping->destination_set ? $mapping->destination_set->name : undef; - my $tset = $mapping->time_set ? $mapping->time_set->name : undef; - my $sset = $mapping->source_set ? $mapping->source_set->name : undef; push @{ $resource->{$mapping->type} }, { - destinationset => $dset, - timeset => $tset, - sourceset => $sset, + $mapping->destination_set ? ( + destinationset => $mapping->destination_set->name, + destinationset_id => $mapping->destination_set->id, + ) : ( + destinationset => undef, + destinationset_id => undef, + ), + $mapping->time_set ? ( + timeset => $mapping->time_set->name, + timeset_id => $mapping->time_set->id, + ) : ( + timeset => undef, + timeset_id => undef, + ), + $mapping->source_set ? ( + sourceset => $mapping->source_set->name, + sourceset_id => $mapping->source_set->id, + ) : ( + sourceset => undef, + sourceset_id => undef, + ), }; } + my $adm = $c->user->roles eq "admin" || $c->user->roles eq "reseller"; + my $hal = NGCP::Panel::Utils::DataHal->new( links => [ NGCP::Panel::Utils::DataHalLink->new( @@ -54,9 +71,8 @@ sub hal_from_item { NGCP::Panel::Utils::DataHalLink->new(relation => 'collection', href => sprintf("%s", $self->dispatch_path)), NGCP::Panel::Utils::DataHalLink->new(relation => 'profile', href => 'http://purl.org/sipwise/ngcp-api/'), NGCP::Panel::Utils::DataHalLink->new(relation => 'self', href => sprintf("%s%d", $self->dispatch_path, $item->id)), - NGCP::Panel::Utils::DataHalLink->new(relation => "ngcp:$type", href => sprintf("/api/%s/%d", $type, $item->id)), NGCP::Panel::Utils::DataHalLink->new(relation => "ngcp:subscribers", href => sprintf("/api/subscribers/%d", $b_subs_id)), - $self->get_journal_relation_link($item->id), + $adm ? $self->get_journal_relation_link($item->id) : (), ], relation => 'ngcp:'.$self->resource_name, ); @@ -69,6 +85,7 @@ sub hal_from_item { run => 0, ); $resource->{cft_ringtimeout} = $ringtimeout_preference; + $resource->{id} = int($item->id); $hal->resource($resource); return $hal; } @@ -90,7 +107,7 @@ sub _item_rs { }); } elsif($c->user->roles eq "subscriber" || $c->user->roles eq "subscriberadmin") { $item_rs = $item_rs->search({ - 'uuid' => $c->user->uuid, + 'me.uuid' => $c->user->uuid, }); } @@ -138,31 +155,64 @@ sub update_item { $cf_preferences{$type} = NGCP::Panel::Utils::Preferences::get_usr_preference_rs( c => $c, prov_subscriber => $item->provisioning_voip_subscriber, attribute => $type); for my $mapping (@{ $resource->{$type} }) { - unless ($mapping->{destinationset}) { - $self->error($c, HTTP_UNPROCESSABLE_ENTITY, "Invalid field 'destinationset' in '$type'. Must be defined."); + my $dset; + if(defined $mapping->{destinationset_id}) { + $dset = $dsets_rs->find({ + subscriber_id => $p_subs_id, + id => $mapping->{destinationset_id}, + }); + } elsif($mapping->{destinationset}) { + $dset = $dsets_rs->find({ + subscriber_id => $p_subs_id, + name => $mapping->{destinationset}, + }); + } else { + $self->error($c, HTTP_UNPROCESSABLE_ENTITY, "Missing field 'destinationset' or 'destinationset_id' in '$type'."); return; } - my $dset = $dsets_rs->find({subscriber_id => $p_subs_id, name => $mapping->{destinationset}, }); unless ($dset) { $self->error($c, HTTP_UNPROCESSABLE_ENTITY, "Invalid 'destinationset'. Could not be found."); return; } - my $tset; - if ($mapping->{timeset}) { - $tset = $tsets_rs->find({subscriber_id => $p_subs_id, name => $mapping->{timeset}, }); - unless ($tset) { - $self->error($c, HTTP_UNPROCESSABLE_ENTITY, "Invalid 'timeset'. Could not be found."); - return; - } + + my $tset; my $has_tset; + if (defined $mapping->{timeset_id}) { + $tset = $tsets_rs->find({ + subscriber_id => $p_subs_id, + id => $mapping->{timeset_id}, + }); + $has_tset = 1; + } elsif (defined $mapping->{timeset}) { + $tset = $tsets_rs->find({ + subscriber_id => $p_subs_id, + name => $mapping->{timeset}, + }); + $has_tset = 1; } - my $sset; - if ($mapping->{sourceset}) { - $sset = $ssets_rs->find({subscriber_id => $p_subs_id, name => $mapping->{sourceset}, }); - unless ($sset) { - $self->error($c, HTTP_UNPROCESSABLE_ENTITY, "Invalid 'sourceset'. Could not be found."); - return; - } + if($has_tset && !$tset) { + $self->error($c, HTTP_UNPROCESSABLE_ENTITY, "Invalid 'timeset'. Could not be found."); + return; } + + my $sset; my $has_sset; + if (defined $mapping->{sourceset_id}) { + $sset = $ssets_rs->find({ + subscriber_id => $p_subs_id, + id => $mapping->{sourceset_id}, + }); + $has_sset = 1; + } elsif (defined $mapping->{sourceset}) { + $sset = $ssets_rs->find({ + subscriber_id => $p_subs_id, + name => $mapping->{sourceset}, + }); + $has_sset = 1; + } + if($has_sset && !$sset) { + $self->error($c, HTTP_UNPROCESSABLE_ENTITY, "Invalid 'sourceset'. Could not be found."); + return; + } + push @new_mappings, $mappings_rs->new_result({ destination_set_id => $dset->id, time_set_id => $tset ? $tset->id : undef, diff --git a/lib/NGCP/Panel/Role/API/CFSourceSets.pm b/lib/NGCP/Panel/Role/API/CFSourceSets.pm index 1a27d22bed..5814e47b67 100644 --- a/lib/NGCP/Panel/Role/API/CFSourceSets.pm +++ b/lib/NGCP/Panel/Role/API/CFSourceSets.pm @@ -12,11 +12,21 @@ use NGCP::Panel::Utils::DataHalLink qw(); use HTTP::Status qw(:constants); use JSON::Types; use NGCP::Panel::Utils::Subscriber; -use NGCP::Panel::Form::CFSourceSetAPI; + +use NGCP::Panel::Form::CallForward::CFSourceSetSubAPI; +#use NGCP::Panel::Form::CallForward::CFSourceSetSubadminAPI; +use NGCP::Panel::Form::CallForward::CFSourceSetAPI; sub get_form { my ($self, $c) = @_; - return NGCP::Panel::Form::CFSourceSetAPI->new; + if($c->user->roles eq "subscriber") { + return NGCP::Panel::Form::CallForward::CFSourceSetSubAPI->new; + } elsif($c->user->roles eq "subscriberadmin") { + #return NGCP::Panel::Form::CallForward::CFSourceSetSubadminAPI->new; + return NGCP::Panel::Form::CallForward::CFSourceSetSubAPI->new; + } else { + return NGCP::Panel::Form::CallForward::CFSourceSetAPI->new; + } } sub hal_from_item { @@ -33,6 +43,7 @@ sub hal_from_item { my $b_subs_id = $item->subscriber->voip_subscriber->id; $resource{subscriber_id} = $b_subs_id; + my $adm = $c->user->roles eq "admin" || $c->user->roles eq "reseller"; my $hal = NGCP::Panel::Utils::DataHal->new( links => [ @@ -45,9 +56,8 @@ sub hal_from_item { NGCP::Panel::Utils::DataHalLink->new(relation => 'collection', href => sprintf("%s", $self->dispatch_path)), NGCP::Panel::Utils::DataHalLink->new(relation => 'profile', href => 'http://purl.org/sipwise/ngcp-api/'), NGCP::Panel::Utils::DataHalLink->new(relation => 'self', href => sprintf("%s%d", $self->dispatch_path, $item->id)), - NGCP::Panel::Utils::DataHalLink->new(relation => "ngcp:$type", href => sprintf("/api/%s/%d", $type, $item->id)), NGCP::Panel::Utils::DataHalLink->new(relation => "ngcp:subscribers", href => sprintf("/api/subscribers/%d", $b_subs_id)), - $self->get_journal_relation_link($item->id), + $adm ? $self->get_journal_relation_link($item->id) : (), ], relation => 'ngcp:'.$self->resource_name, ); @@ -78,7 +88,8 @@ sub _item_rs { } , { join => {'subscriber' => {'contract' => 'contact'} }, }); - } elsif($c->user->role eq "subscriberadmin" || $c->user->roles eq "subscriber") { + # TODO: do we want subscriberadmins to update other subs' entries? + } elsif($c->user->roles eq "subscriberadmin" || $c->user->roles eq "subscriber") { $item_rs = $c->model('DB')->resultset('voip_cf_source_sets') ->search_rs({ 'subscriber_id' => $c->user->id, @@ -116,6 +127,13 @@ sub update_item { return; } + if($c->user->roles eq "subscriber" || $c->user->roles eq "subscriberadmin") { + $resource->{subscriber_id} = $c->user->voip_subscriber->id; + } + # elsif($c->user->roles eq "subscriberadmin") { + # $resource->{subscriber_id} //= $c->user->id; + #} + my $b_subscriber = $schema->resultset('voip_subscribers')->find($resource->{subscriber_id}); unless ($b_subscriber) { $self->error($c, HTTP_UNPROCESSABLE_ENTITY, "Invalid 'subscriber_id'."); @@ -126,6 +144,10 @@ sub update_item { $self->error($c, HTTP_UNPROCESSABLE_ENTITY, "Invalid subscriber."); last; } + #if($c->user->roles eq "subscriberadmin" && $subscriber->account_id != $c->user->account_id) { + # $self->error($c, HTTP_UNPROCESSABLE_ENTITY, "Invalid subscriber."); + # last; + #} try { $item->update({ diff --git a/lib/NGCP/Panel/Role/API/CFTimeSets.pm b/lib/NGCP/Panel/Role/API/CFTimeSets.pm index 950e5874b1..8bd62e2d94 100644 --- a/lib/NGCP/Panel/Role/API/CFTimeSets.pm +++ b/lib/NGCP/Panel/Role/API/CFTimeSets.pm @@ -12,11 +12,17 @@ use NGCP::Panel::Utils::DataHalLink qw(); use HTTP::Status qw(:constants); use JSON::Types; use NGCP::Panel::Utils::Subscriber; -use NGCP::Panel::Form::CFTimeSetAPI; + +use NGCP::Panel::Form::CallForward::CFTimeSetSubAPI; +use NGCP::Panel::Form::CallForward::CFTimeSetAPI; sub get_form { my ($self, $c) = @_; - return NGCP::Panel::Form::CFTimeSetAPI->new; + if($c->user->roles eq "subscriber" || $c->user->roles eq "subscriberadmin") { + return NGCP::Panel::Form::CallForward::CFTimeSetSubAPI->new; + } else { + return NGCP::Panel::Form::CallForward::CFTimeSetAPI->new; + } } sub hal_from_item { @@ -79,7 +85,7 @@ sub _item_rs { } , { join => {'subscriber' => {'contract' => 'contact'} }, }); - } elsif($c->user->role eq "subscriberadmin" || $c->user->roles eq "subscriber") { + } elsif($c->user->roles eq "subscriberadmin" || $c->user->roles eq "subscriber") { $item_rs = $c->model('DB')->resultset('voip_cf_time_sets') ->search_rs({ 'subscriber_id' => $c->user->id, @@ -117,6 +123,10 @@ sub update_item { return; } + if($c->user->roles eq "subscriber" || $c->user->roles eq "subscriberadmin") { + $resource->{subscriber_id} = $c->user->voip_subscriber->id; + } + my $b_subscriber = $schema->resultset('voip_subscribers')->find($resource->{subscriber_id}); unless ($b_subscriber) { $self->error($c, HTTP_UNPROCESSABLE_ENTITY, "Invalid 'subscriber_id'."); diff --git a/lib/NGCP/Panel/Role/API/Subscribers.pm b/lib/NGCP/Panel/Role/API/Subscribers.pm index fe8b49eb78..485c143e2a 100644 --- a/lib/NGCP/Panel/Role/API/Subscribers.pm +++ b/lib/NGCP/Panel/Role/API/Subscribers.pm @@ -24,10 +24,8 @@ sub get_form { my ($self, $c) = @_; if($c->user->roles eq "admin" || $c->user->roles eq "reseller") { - $c->log->error("++++ admin form"); return NGCP::Panel::Form::Subscriber::SubscriberAPI->new(ctx => $c); } elsif($c->user->roles eq "subscriberadmin" || $c->user->roles eq "subscriber") { - $c->log->error("++++ subscriber form"); return NGCP::Panel::Form::Subscriber::SubscriberSubAdminAPI->new(ctx => $c); } } diff --git a/t/api-rest/api-subscriber-cf.t b/t/api-rest/api-subscriber-cf.t new file mode 100644 index 0000000000..91bdb0400b --- /dev/null +++ b/t/api-rest/api-subscriber-cf.t @@ -0,0 +1,208 @@ +#!/usr/bin/perl -w +use strict; + +use NGCP::Test; +use Test::More; +use JSON qw/from_json to_json/; +use Data::Dumper; + +my $test = NGCP::Test->new(log_debug => 1); +my $t = $test->generate_sid(); +my $c = $test->client(); + +my $ref = $test->reference_data( + client => $c, + use_persistent => 1, + delete_persistent => 0, + depends => [ + { + resource => 'subscribers', + name => 'my_seat_subscriber', + hints => [{ name => 'pbx seat subscriber'}] + } + ], +); + +my $dom = $ref->data('my_seat_subscriber')->{domain}; +my $c_sub_ext = $test->client( + role => 'subscriber', + username => + $ref->data('my_seat_subscriber')->{webusername} . '@' . $dom, + password => $ref->data('my_seat_subscriber')->{webpassword}, +); + +diag("test destination set as subscriber"); +my $cf_dstset = $test->resource( + client => $c_sub_ext, + resource => 'cfdestinationsets', + data => { + name => 'test cf destination set', + destinations => [ + # TODO: we have to define all fields here to pass deep testing + { destination => "sip:12340\@$dom", timeout => 180, priority => 1, announcement_id => undef, simple_destination => '12340' }, + { destination => "sip:12341\@$dom", timeout => 180, priority => 2, announcement_id => undef, simple_destination => '12341' }, + { destination => "sip:12342\@$dom", timeout => 180, priority => 3, announcement_id => undef, simple_destination => '12342' }, + ], + }, +); + +$cf_dstset->test_post( + name => 'create destination sets', + expected_result => { 'code' => 201 }, +); + +my $dstset = $cf_dstset->pop_created_item(); + +$cf_dstset->test_put( + name => "test update", + item => $dstset, + data_replace => { + field => 'destinations', value => [ + { destination => "sip:22340\@$dom", timeout => 180, priority => 1, announcement_id => undef, simple_destination => '22340' }, + { destination => "sip:22341\@$dom", timeout => 180, priority => 2, announcement_id => undef, simple_destination => '22341' }, + { destination => "sip:22342\@$dom", timeout => 180, priority => 3, announcement_id => undef, simple_destination => '22342' }, + ], + }, + expected_result => { code => 200 }, +); + +diag("test source set as subscriber"); +my $cf_srcset = $test->resource( + client => $c_sub_ext, + resource => 'cfsourcesets', + data => { + name => 'test cf source set', + mode => 'whitelist', + sources => [ + { source => "1235*" }, + { source => "1236" }, + { source => "1237[1-5]" }, + ], + }, +); + +$cf_srcset->test_post( + name => 'create source sets', + expected_result => { 'code' => 201 }, +); + +my $srcset = $cf_srcset->pop_created_item(); + +$cf_srcset->test_put( + name => "test update", + item => $srcset, + data_replace => { + field => 'mode', value => 'blacklist', + }, + expected_result => { code => 200 }, +); + +diag("test time set as subscriber"); +my $cf_timset = $test->resource( + client => $c_sub_ext, + resource => 'cftimesets', + data => { + name => 'test cf time set', + times => [ + { year => "2017", month => undef, mday => undef, wday => undef, hour => undef, minute => undef }, + { year => "2018-2019", month => undef, mday => undef, wday => undef, hour => undef, minute => undef }, + { year => undef, month => undef, mday => "10-20", wday => undef, hour => undef, minute => undef }, + ], + }, +); + +$cf_timset->test_post( + name => 'create time sets', + expected_result => { 'code' => 201 }, +); + +my $timset = $cf_timset->pop_created_item(); + +$cf_timset->test_put( + name => "test update", + item => $timset, + data_replace => { + field => 'times', value => [ + { year => "2020", month => undef, mday => undef, wday => undef, hour => undef, minute => undef }, + ] + }, + expected_result => { code => 200 }, +); + +diag("test cf mappings"); + +my $cf_map = $test->resource( + client => $c_sub_ext, + resource => 'cfmappings', + data => { + cfu => [], + cfb => [], + cft => [], + cfna => [], + cfs => [], + cft_ringtimeout => undef, + } +); + +my $mappings = $cf_map->test_get( + name => 'prefetch cf mappings', + expected_links => [qw/ + ngcp:subscribers + /], + expected_result => { 'code' => 200 }, +); +my $mapping = $mappings->[0]->{_embedded}->{'ngcp:cfmappings'}->[0]; + +my $cfu = [{ + destinationset => $dstset->{name}, + destinationset_id => $dstset->{id}, + sourceset => $srcset->{name}, + sourceset_id => $srcset->{id}, + timeset => $timset->{name}, + timeset_id => $timset->{id}, +}]; + +$mappings = $cf_map->test_put( + name => "update cfmapping", + item => $mapping, + data_replace => { + field => 'cfu', value => $cfu, + }, + expected_result => { code => 200 }, +); +ok(@{ $mappings->[0]->{cfu} } > 0, "test size of postfetched cfu mapping"); +$test->inc_test_count(); + +$cf_dstset->test_delete( + name => "test delete", + item => $dstset, + expected_result => { code => 204 }, +); +$cf_srcset->test_delete( + name => "test delete", + item => $srcset, + expected_result => { code => 204 }, +); +$cf_timset->test_delete( + name => "test delete", + item => $timset, + expected_result => { code => 204 }, +); + +$cf_map->test_delete( + name => "test delete", + item => $mapping, + expected_result => { code => 404 }, +); + +$mapping->{cfu} = []; +$mappings = $cf_map->test_get( + name => "refetch cfmapping after delete", + item => $mapping, + expected_links => [qw/ + ngcp:subscribers + /], + expected_result => { code => 200 }, +); + +$test->done(); diff --git a/t/lib/NGCP/Test/Client.pm b/t/lib/NGCP/Test/Client.pm index ce63d34cfb..2b74585025 100644 --- a/t/lib/NGCP/Test/Client.pm +++ b/t/lib/NGCP/Test/Client.pm @@ -161,10 +161,15 @@ has 'last_rtt' => ( sub _request { my ($self, $req) = @_; my $t0 = [gettimeofday]; + $self->_test->debug("content of " . $req->method . " request to " . $req->uri . ":\n"); + $self->_test->debug($req->content || ""); + $self->_test->debug("\n"); my $res = $self->_ua->request($req); my $rtt = tv_interval($t0); $self->last_rtt($rtt); - $self->_test->debug($res->decoded_content // ''); + $self->_test->debug("content of response:\n"); + $self->_test->debug($res->decoded_content || ""); + $self->_test->debug("\n"); return $res; } diff --git a/t/lib/NGCP/Test/ReferenceData.pm b/t/lib/NGCP/Test/ReferenceData.pm index 4281a4d5d5..aa4901c7b6 100644 --- a/t/lib/NGCP/Test/ReferenceData.pm +++ b/t/lib/NGCP/Test/ReferenceData.pm @@ -192,7 +192,8 @@ sub _read_depends { #$self->_test->debug("+++ checking hint field $$hint{field} with expected value $$hint{value} against ".$ref->{data}->{$hint->{field}}."\n"); if(defined $hint->{name} && $hint->{name} eq $ref->{name}) { $hint_found = 1; - $self->_dependstree->{$d->{name}} = $ref->{data}; + $self->_dependstree->{$d->{name}}->{data} = $ref->{data}; + $self->_dependstree->{$d->{name}}->{meta}->{type} = $ref->{type}; last; } elsif(defined $hint->{field} && exists $ref->{data}->{$hint->{field}} && "".$hint->{value} eq "".$ref->{data}->{$hint->{field}}) { @@ -200,7 +201,8 @@ sub _read_depends { #$self->_test->debug("found reference data using hints:\n"); #$self->_test->debug Dumper $ref->{data}; $hint_found = 1; - $self->_dependstree->{$d->{name}} = $ref->{data}; + $self->_dependstree->{$d->{name}}->{data} = $ref->{data}; + $self->_dependstree->{$d->{name}}->{meta}->{type} = $ref->{type}; last; } } @@ -212,7 +214,8 @@ sub _read_depends { #$self->_test->debug("found reference data:\n"); #$self->_test->debug Dumper $ref->{data}; $resource_found = 1; - $self->_dependstree->{$d->{name}} = $ref->{data}; + $self->_dependstree->{$d->{name}}->{data} = $ref->{data}; + $self->_dependstree->{$d->{name}}->{meta}->{type} = $ref->{type}; last; } } @@ -317,8 +320,26 @@ sub _resolve_deps { sub data { my ($self, $name) = @_; - if(exists $self->_dependstree->{$name}) { - return $self->_dependstree->{$name}; + my $ref = $self->_dependstree->{$name}; + if($ref) { + if($ref->{meta}->{full}) { + return $ref->{data}; + } else { + $self->_test->debug("$name not fully fetched yet\n"); + my $res = $self->client->_get( + 'api/'.$ref->{meta}->{type}.'/'.$ref->{data}->{id} + ); + unless($res->is_success) { + die "Failed to fully fetch " . + "$ref->{meta}->{type} #$ref->{data}->{id}: " . + $res->status_line."\n"; + } + my $data = from_json($res->decoded_content); + $self->_dependstree->{$name}->{data} = $data; + $self->_test->debug("$name now fully fetched yet\n"); + $self->_dependstree->{$name}->{meta}->{full} = 1; + return $data; + } } else { die "Failed to find given dependency name '$name' in dependency tree, check name against 'depends' in constructor\n"; } diff --git a/t/lib/NGCP/Test/ReferenceData/pbxsubscribers.json b/t/lib/NGCP/Test/ReferenceData/pbxsubscribers.json index e88c5b8082..a8d25aa2a5 100644 --- a/t/lib/NGCP/Test/ReferenceData/pbxsubscribers.json +++ b/t/lib/NGCP/Test/ReferenceData/pbxsubscribers.json @@ -21,5 +21,29 @@ "customer_id": "${filled pbxaccount customer}", "status": "active" } + }, + { + "name": "pbx seat subscriber", + "type": "subscribers", + "depends": [ + "pbx pilot subscriber", + "filled pbxaccount customer", + "domain" + ], + "data": { + "username": "pbx_seat_user_${sid}", + "password": "pbx_seat_pass_${sid}", + "webusername": "pbx_seat_webuser_${sid}", + "webpassword": "pbx_seat_webpass_${sid}", + "display_name": "pbx seat display ${sid}", + "primary_number": { + "cc": "43", "ac": "998", "sn": "${sid}" + }, + "administrative": false, + "domain_id": "${domain}", + "customer_id": "${filled pbxaccount customer}", + "status": "active", + "pbx_extension": "101" + } } ] diff --git a/t/lib/NGCP/Test/Resource.pm b/t/lib/NGCP/Test/Resource.pm index 3e8a738182..b555801266 100644 --- a/t/lib/NGCP/Test/Resource.pm +++ b/t/lib/NGCP/Test/Resource.pm @@ -238,6 +238,9 @@ sub _test_fields { my $k = (keys %{ $skip{$field} })[0]; my $refold = delete $refelem->{$k}; my $old = delete $elem->{$k}; + $self->_test->debug("compare content of $field\n"); + $self->_test->debug("element: " . Dumper $elem); + $self->_test->debug("reference element: " . Dumper $refelem); is_deeply($elem, $refelem, $name . " - content of $field"); $self->_inc_test_count; $refelem->{$k} = $refold; @@ -246,6 +249,9 @@ sub _test_fields { is(@{ $ref->{$field} }, @{ $item->{$field} }, "$name - element count of $field"); $self->_inc_test_count; } else { + $self->_test->debug("compare content of $field via bag comparison\n"); + $self->_test->debug("element: " . Dumper $item->{$field}); + $self->_test->debug("reference element: " . Dumper $ref->{$field}); cmp_bag($item->{$field}, $ref->{$field}, $name . " - content of $field"); $self->_inc_test_count; } @@ -668,6 +674,7 @@ sub test_put { for(my $i = 0; $i < @data; ++$i) { my $d = $self->_get_replaced_data($item, $data[$i]); + $self->_test->debug("replaced data: " . Dumper $d); my $e = $expected[$i]; my $name = "$testname $i";