diff --git a/lib/NGCP/Panel/Controller/API/MetaConfigDefs.pm b/lib/NGCP/Panel/Controller/API/MetaConfigDefs.pm index 7d230edde1..8a8f9eb3ae 100644 --- a/lib/NGCP/Panel/Controller/API/MetaConfigDefs.pm +++ b/lib/NGCP/Panel/Controller/API/MetaConfigDefs.pm @@ -68,6 +68,7 @@ sub GET :Allow { $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{acl} = $config_internal{acl}; $config{features} = $config_internal{features}; my $meta = { diff --git a/lib/NGCP/Panel/Controller/API/Subscribers.pm b/lib/NGCP/Panel/Controller/API/Subscribers.pm index 95fcdd7e7b..c16cad787b 100644 --- a/lib/NGCP/Panel/Controller/API/Subscribers.pm +++ b/lib/NGCP/Panel/Controller/API/Subscribers.pm @@ -2,6 +2,7 @@ package NGCP::Panel::Controller::API::Subscribers; use NGCP::Panel::Utils::Generic qw(:all); use Sipwise::Base; +use parent qw/NGCP::Panel::Role::Entities NGCP::Panel::Role::API::Subscribers/; use boolean qw(true); use Data::HAL qw(); @@ -16,6 +17,10 @@ use NGCP::Panel::Utils::ProfilePackages qw(); use NGCP::Panel::Utils::Events qw(); use UUID; +__PACKAGE__->set_config({ + allowed_roles => [qw/admin reseller subscriberadmin subscriber/], +}); + sub allowed_methods{ return [qw/GET POST OPTIONS HEAD/]; } @@ -258,24 +263,6 @@ sub query_params { return $params; } -use parent qw/NGCP::Panel::Role::Entities NGCP::Panel::Role::API::Subscribers/; - -sub resource_name{ - return 'subscribers'; -} - -sub dispatch_path{ - return '/api/subscribers/'; -} - -sub relation{ - return 'http://purl.org/sipwise/ngcp-api/#rel-subscribers'; -} - -__PACKAGE__->set_config({ - allowed_roles => [qw/admin reseller subscriberadmin subscriber/], -}); - sub GET :Allow { my ($self, $c) = @_; my $page = $c->request->params->{page} // 1; @@ -341,21 +328,6 @@ sub GET :Allow { sub POST :Allow { my ($self, $c) = @_; - if($c->user->roles eq "admin" || $c->user->roles eq "reseller") { - } elsif($c->user->roles eq "subscriber") { - $self->error($c, HTTP_FORBIDDEN, "Read-only resource for authenticated role"); - return; - } elsif($c->user->roles eq "subscriberadmin") { - unless($c->config->{features}->{cloudpbx}) { - $self->error($c, HTTP_FORBIDDEN, "Read-only resource for authenticated role"); - return; - } - my $customer = $self->get_customer($c, $c->user->account_id); - if($customer->get_column('product_class') ne 'pbxaccount') { - $self->error($c, HTTP_FORBIDDEN, "Read-only resource for authenticated role"); - return; - } - } my $schema = $c->model('DB'); $schema->set_transaction_isolation('READ COMMITTED'); diff --git a/lib/NGCP/Panel/Controller/API/SubscribersItem.pm b/lib/NGCP/Panel/Controller/API/SubscribersItem.pm index a170db820f..44a5c11db7 100644 --- a/lib/NGCP/Panel/Controller/API/SubscribersItem.pm +++ b/lib/NGCP/Panel/Controller/API/SubscribersItem.pm @@ -21,29 +21,19 @@ sub allowed_methods{ use parent qw/NGCP::Panel::Role::EntitiesItem NGCP::Panel::Role::API::Subscribers/; -sub resource_name{ - return 'subscribers'; -} - -sub dispatch_path{ - return '/api/subscribers/'; -} +__PACKAGE__->set_config({ + allowed_roles => { + Default => [qw/admin reseller subscriberadmin subscriber/], + Journal => [qw/admin reseller/], + } +}); -sub relation{ - return 'http://purl.org/sipwise/ngcp-api/#rel-subscribers'; -} sub journal_query_params { my($self,$query_params) = @_; return $self->get_journal_query_params($query_params); } -__PACKAGE__->set_config({ - allowed_roles => { - Default => [qw/admin reseller subscriberadmin subscriber/], - Journal => [qw/admin reseller/], - } -}); sub GET :Allow { my ($self, $c, $id) = @_; @@ -81,10 +71,7 @@ sub GET :Allow { sub PUT :Allow { my ($self, $c, $id) = @_; - if($c->user->roles eq "subscriberadmin" || $c->user->roles eq "subscriber") { - $self->error($c, HTTP_FORBIDDEN, "Read-only resource for authenticated role"); - return; - } + return unless $self->check_write_access($c); my $schema = $c->model('DB'); $schema->set_transaction_isolation('READ COMMITTED'); @@ -118,21 +105,7 @@ sub PUT :Allow { last unless $self->add_update_journal_item_hal($c,$hal); $guard->commit; - - if ('minimal' eq $preference) { - $c->response->status(HTTP_NO_CONTENT); - $c->response->header(Preference_Applied => 'return=minimal'); - $c->response->body(q()); - } else { - #$resource = $self->resource_from_item($c, $subscriber, $form); - #my $hal = $self->hal_from_item($c, $subscriber, $resource, $form); - my $response = HTTP::Response->new(HTTP_OK, undef, HTTP::Headers->new( - $hal->http_headers, - ), $hal->as_json); - $c->response->headers($response->headers); - $c->response->header(Preference_Applied => 'return=representation'); - $c->response->body($response->content); - } + $self->return_representation($c, 'hal' => $hal, 'preference' => $preference ); } return; } @@ -140,10 +113,7 @@ sub PUT :Allow { sub PATCH :Allow { my ($self, $c, $id) = @_; - if($c->user->roles eq "subscriberadmin" || $c->user->roles eq "subscriber") { - $self->error($c, HTTP_FORBIDDEN, "Read-only resource for authenticated role"); - return; - } + return unless $self->check_write_access($c); my $schema = $c->model('DB'); $schema->set_transaction_isolation('READ COMMITTED'); @@ -184,21 +154,7 @@ sub PATCH :Allow { last unless $self->add_update_journal_item_hal($c,$hal); $guard->commit; - - if ('minimal' eq $preference) { - $c->response->status(HTTP_NO_CONTENT); - $c->response->header(Preference_Applied => 'return=minimal'); - $c->response->body(q()); - } else { - #$resource = $self->resource_from_item($c, $subscriber, $form); - #my $hal = $self->hal_from_item($c, $subscriber, $resource, $form); - my $response = HTTP::Response->new(HTTP_OK, undef, HTTP::Headers->new( - $hal->http_headers, - ), $hal->as_json); - $c->response->headers($response->headers); - $c->response->header(Preference_Applied => 'return=representation'); - $c->response->body($response->content); - } + $self->return_representation($c, 'hal' => $hal, 'preference' => $preference ); } return; } @@ -206,10 +162,7 @@ sub PATCH :Allow { sub DELETE :Allow { my ($self, $c, $id) = @_; - if($c->user->roles eq "subscriber") { - $self->error($c, HTTP_FORBIDDEN, "Read-only resource for authenticated role"); - return; - } + return unless $self->check_write_access($c); my $guard = $c->model('DB')->txn_scope_guard; { @@ -217,12 +170,6 @@ sub DELETE :Allow { last unless $self->resource_exists($c, subscriber => $subscriber); if($c->user->roles eq "subscriberadmin") { - my $customer = $self->get_customer($c, $c->user->account_id); - if($customer->get_column('product_class') ne 'pbxaccount') { - $self->error($c, HTTP_FORBIDDEN, "Read-only resource for authenticated role"); - return; - } - my $prov_sub = $subscriber->provisioning_voip_subscriber; if($prov_sub->is_pbx_pilot) { $self->error($c, HTTP_FORBIDDEN, "Cannot terminate pilot subscriber"); diff --git a/lib/NGCP/Panel/Role/API/Subscribers.pm b/lib/NGCP/Panel/Role/API/Subscribers.pm index 4155c557e5..c12091e055 100644 --- a/lib/NGCP/Panel/Role/API/Subscribers.pm +++ b/lib/NGCP/Panel/Role/API/Subscribers.pm @@ -5,7 +5,6 @@ use Sipwise::Base; use parent 'NGCP::Panel::Role::API'; - use boolean qw(true); use Data::HAL qw(); use Data::HAL::Link qw(); @@ -19,6 +18,18 @@ use NGCP::Panel::Utils::Subscriber; use NGCP::Panel::Utils::Events; use NGCP::Panel::Utils::DateTime; +sub resource_name{ + return 'subscribers'; +} + +sub dispatch_path{ + return '/api/subscribers/'; +} + +sub relation{ + return 'http://purl.org/sipwise/ngcp-api/#rel-subscribers'; +} + sub get_form { my ($self, $c) = @_; @@ -119,7 +130,7 @@ sub resource_from_item { # 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") { + if ($c->user->roles eq "admin" || $c->user->roles eq "reseller") { $resource{customer_id} = $contract_id; $resource{uuid} = $item->uuid; @@ -134,16 +145,17 @@ sub resource_from_item { $resource{lock} = undef; } } else { - # fields we never want to see - foreach my $k(qw/domain_id status profile_id profile_set_id external_id/) { - delete $resource{$k}; - } + if (!$self->subscriberadmin_write_access($c)) { + # fields we never want to see + foreach my $k(qw/domain_id status profile_id profile_set_id external_id/) { + delete $resource{$k}; + } - # TODO: make custom filtering configurable! - foreach my $k(qw/password webpassword/) { - delete $resource{$k}; + # TODO: make custom filtering configurable! + foreach my $k(qw/password webpassword/) { + delete $resource{$k}; + } } - if($c->user->roles eq "subscriberadmin") { $resource{customer_id} = $contract_id; } @@ -597,10 +609,7 @@ sub prepare_resource { sub update_item { my ($self, $c, $schema, $item, $full_resource, $resource, $form) = @_; - if($c->user->roles eq "subscriberadmin" || $c->user->roles eq "subscriber") { - $self->error($c, HTTP_FORBIDDEN, "Read-only resource for authenticated role"); - return; - } + return unless $self->check_write_access($c); my $subscriber = $item; my $customer = $full_resource->{customer}; @@ -817,5 +826,33 @@ sub update_item { return $subscriber; } +sub check_write_access { + my($self, $c) = @_; + if($c->user->roles eq "admin" || $c->user->roles eq "reseller") { + } elsif($c->user->roles eq "subscriber") { + $self->error($c, HTTP_FORBIDDEN, "Read-only resource for authenticated role"); + return; + } elsif($c->user->roles eq "subscriberadmin") { + unless($c->config->{features}->{cloudpbx}) { + $self->error($c, HTTP_FORBIDDEN, "Read-only resource for authenticated role"); + return; + } + my $customer = $self->get_customer($c, $c->user->account_id); + if($customer->get_column('product_class') ne 'pbxaccount') { + $self->error($c, HTTP_FORBIDDEN, "Read-only resource for authenticated role"); + return; + } + } + return 1; +} + +sub subscriberadmin_write_access { + my($self,$c) = @_; + if ($c->user->roles eq "subscriberadmin" && $c->config->{acl}->{subscriberadmin}->{subscribers} =~/write/ ) { + return 1; + } + return 0; +} + 1; # vim: set tabstop=4 expandtab: diff --git a/t/api-rest/api-numbers.t b/t/api-rest/api-numbers.t index 922471edb2..1aa6e83943 100644 --- a/t/api-rest/api-numbers.t +++ b/t/api-rest/api-numbers.t @@ -1,12 +1,12 @@ use strict; use warnings; +use threads; use Test::Collection; use Test::FakeData; use Test::More; use Data::Dumper; use Clone qw/clone/; -use threads; #use NGCP::Panel::Utils::Subscriber; diff --git a/t/api-rest/api-subscribers.t b/t/api-rest/api-subscribers.t index 3381f1f3b9..05f5918d4b 100644 --- a/t/api-rest/api-subscribers.t +++ b/t/api-rest/api-subscribers.t @@ -331,7 +331,46 @@ if($remote_config->{config}->{features}->{cloudpbx}){ } $test_machine->runas('admin'); } + {#TT#34021 + diag("34021: check subscriberadmin PUT and PATCH. Possible only for pbx subscriberadmin. Access priveleges:".$remote_config->{config}->{acl}->{subscriberadmin}->{subscribers} .";\n"); + my $data = clone $test_machine->DATA_ITEM; + $data->{administrative} = 1; + my $pbxsubscriberadmin = $test_machine->check_create_correct(1, sub { + my $num = $_[1]->{i}; + $_[0]->{administrative} = 1; + $_[0]->{webusername} .= time().'_34021'; + $_[0]->{webpassword} = 'api_test_webpassword'; + $_[0]->{username} .= time().'_34021' ; + $_[0]->{pbx_extension} .= '34021'; + $_[0]->{primary_number}->{ac} .= '34021'; + $_[0]->{is_pbx_group} = 0; + $_[0]->{is_pbx_pilot} = ($pilot || $_[1]->{i} > 1)? 0 : 1; + delete $_[0]->{alias_numbers}; + } )->[0]; + $test_machine->set_subscriber_credentials($pbxsubscriberadmin->{content}); + $test_machine->runas('subscriber'); + my $subscriber = $test_machine->check_create_correct(1, sub { + my $num = $_[1]->{i}; + $_[0]->{webusername} .= time().'_34021_1'; + $_[0]->{webpassword} = 'api_test_webpassword'; + $_[0]->{username} .= time().'_34021_1' ; + $_[0]->{pbx_extension} .= '340211'; + $_[0]->{primary_number}->{ac} .= '34021'; + $_[0]->{is_pbx_group} = 0; + $_[0]->{is_pbx_pilot} = 0; + delete $_[0]->{alias_numbers}; + } )->[0]; + if ($remote_config->{config}->{acl}->{subscriberadmin}->{subscribers} =~/write/) { + $test_machine->check_get2put($subscriber,{},$put2get_check_params); + my($res,$content,$req) = $test_machine->request_patch( [ { op => 'replace', path => '/display_name', value => 'patched 34021' } ], $subscriber->{location} ); + $test_machine->http_code_msg(200, "Check display_name patch for subscriberadmin", $res, $content); + }else{ + my($res,$content,$req) = $test_machine->request_patch( [ { op => 'replace', path => '/display_name', value => 'patched 34021' } ], $subscriber->{location} ); + $test_machine->http_code_msg(403, "Check display_name patch for subscriberadmin", $res, $content, "Read-only resource for authenticated role"); + } + } } + #TT#21818 variant 2 - pbx feature off, subscriberadmin is read-only. No subscriber exists if (!$remote_config->{config}->{features}->{cloudpbx}) { diag("21818: check password validation: subscriber and subscriberadmin are read-only roles;\n"); @@ -385,10 +424,10 @@ done_testing; #--------- aux sub check_password_validation_config{ if( - $remote_config->{config}->{security}->{password_sip_validate} - && - $remote_config->{config}->{security}->{password_web_validate} - && + $remote_config->{config}->{security}->{password_sip_validate} + && + $remote_config->{config}->{security}->{password_web_validate} + && ok($remote_config->{config}->{security}->{password_sip_validate},"check www_admin.security.password_sip_validate should be true.") && ok($remote_config->{config}->{security}->{password_web_validate},"check www_admin.security.password_web_validate should be true.")) { @@ -396,6 +435,7 @@ sub check_password_validation_config{ } return 0; } + sub test_password_validation { my ($subscriber_put, $actions) = @_; my %fields = ('web' => 'web%s','' => '%s'); diff --git a/t/lib/Test/Collection.pm b/t/lib/Test/Collection.pm index 72c8a0d59e..549a9dca05 100644 --- a/t/lib/Test/Collection.pm +++ b/t/lib/Test/Collection.pm @@ -1222,6 +1222,7 @@ sub check_get2put{ $get_in //= {}; $put_in //= {}; + $put_in->{uri} //= $put_in->{location}; $get_in->{uri} //= $put_in->{uri}; $put_in->{uri} //= $get_in->{uri};