diff --git a/lib/NGCP/Panel/Controller/API/BannedIps.pm b/lib/NGCP/Panel/Controller/API/BannedIps.pm new file mode 100644 index 0000000000..a82924c3de --- /dev/null +++ b/lib/NGCP/Panel/Controller/API/BannedIps.pm @@ -0,0 +1,28 @@ +package NGCP::Panel::Controller::API::BannedIps; + + +use Sipwise::Base; +use parent qw/NGCP::Panel::Role::Entities NGCP::Panel::Role::API::BannedIps/; + + +use NGCP::Panel::Utils::Peering; +use HTTP::Status qw(:constants); +use NGCP::Panel::Utils::Security; + +__PACKAGE__->set_config(); + +sub allowed_methods { + return [qw/GET OPTIONS HEAD/]; +} + +sub api_description { + return 'Defines banned ips.'; +} + +sub get_list{ + my ($self, $c) = @_; + return NGCP::Panel::Utils::Security::list_banned_ips($c); +} +1; + +# vim: set tabstop=4 expandtab: diff --git a/lib/NGCP/Panel/Controller/API/BannedIpsItem.pm b/lib/NGCP/Panel/Controller/API/BannedIpsItem.pm new file mode 100644 index 0000000000..ea09b64fe6 --- /dev/null +++ b/lib/NGCP/Panel/Controller/API/BannedIpsItem.pm @@ -0,0 +1,26 @@ +package NGCP::Panel::Controller::API::BannedIpsItem; + +use parent qw/NGCP::Panel::Role::EntitiesItem NGCP::Panel::Role::API::BannedIps/; + +use Sipwise::Base; + + +use HTTP::Status qw(:constants); +use NGCP::Panel::Utils::Security; + + +__PACKAGE__->set_config(); + +sub allowed_methods { + return [qw/GET OPTIONS HEAD DELETE/]; +} + +sub delete_item { + my($self, $c, $item, $old_resource, $resource, $form) = @_; + my $ip = $item; + NGCP::Panel::Utils::Security::ip_unban($c, $ip); +} + +1; + +# vim: set tabstop=4 expandtab: diff --git a/lib/NGCP/Panel/Controller/API/BannedUsers.pm b/lib/NGCP/Panel/Controller/API/BannedUsers.pm new file mode 100644 index 0000000000..f416083bc6 --- /dev/null +++ b/lib/NGCP/Panel/Controller/API/BannedUsers.pm @@ -0,0 +1,28 @@ +package NGCP::Panel::Controller::API::BannedUsers; + + +use Sipwise::Base; +use parent qw/NGCP::Panel::Role::Entities NGCP::Panel::Role::API::BannedUsers/; + + +use NGCP::Panel::Utils::Peering; +use HTTP::Status qw(:constants); +use NGCP::Panel::Utils::Security; + +__PACKAGE__->set_config(); + +sub allowed_methods { + return [qw/GET OPTIONS HEAD/]; +} + +sub api_description { + return 'Defines banned users.'; +} + +sub get_list{ + my ($self, $c) = @_; + return NGCP::Panel::Utils::Security::list_banned_users($c, data_for_json => 1); +} +1; + +# vim: set tabstop=4 expandtab: diff --git a/lib/NGCP/Panel/Controller/API/BannedUsersItem.pm b/lib/NGCP/Panel/Controller/API/BannedUsersItem.pm new file mode 100644 index 0000000000..c8174802ef --- /dev/null +++ b/lib/NGCP/Panel/Controller/API/BannedUsersItem.pm @@ -0,0 +1,26 @@ +package NGCP::Panel::Controller::API::BannedUsersItem; + +use parent qw/NGCP::Panel::Role::EntitiesItem NGCP::Panel::Role::API::BannedUsers/; + +use Sipwise::Base; + + +use HTTP::Status qw(:constants); +use NGCP::Panel::Utils::Security; + + +__PACKAGE__->set_config(); + +sub allowed_methods { + return [qw/GET OPTIONS HEAD DELETE/]; +} + +sub delete_item { + my($self, $c, $item, $old_resource, $resource, $form) = @_; + my $user = $item; + NGCP::Panel::Utils::Security::user_unban($c, $user); +} + +1; + +# vim: set tabstop=4 expandtab: diff --git a/lib/NGCP/Panel/Controller/Security.pm b/lib/NGCP/Panel/Controller/Security.pm index d7b3c79eb0..980ad2eb57 100644 --- a/lib/NGCP/Panel/Controller/Security.pm +++ b/lib/NGCP/Panel/Controller/Security.pm @@ -4,10 +4,8 @@ use Sipwise::Base; use parent 'Catalyst::Controller'; -use XML::LibXML; -use URI::Encode; +use NGCP::Panel::Utils::Security; use NGCP::Panel::Utils::Navigation; -use NGCP::Panel::Utils::XMLDispatcher; use NGCP::Panel::Utils::DateTime; sub auto :Does(ACL) :ACLDetachTo('/denied_page') :AllowedRole(admin) { @@ -23,74 +21,14 @@ sub root :PathPart('/') :CaptureArgs(0) { sub index :Chained('/') :PathPart('security') :Args(0) { my ( $self, $c ) = @_; - my $dispatcher = NGCP::Panel::Utils::XMLDispatcher->new; - my $xml_parser = XML::LibXML->new(); - my $ip_xml = <<'EOF'; - - - htable.dump - - ipban - - -EOF - - my $ip_res = $dispatcher->dispatch($c, "loadbalancer", 1, 1, $ip_xml); - - my @ips = (); - for my $host (grep {$$_[1]} @$ip_res) { - my $xmlDoc = $xml_parser->parse_string($host->[2]); - @ips = map { { ip => $_->to_literal } } - $xmlDoc->findnodes('//member/value/string'); - } - - - my $user_xml = <<'EOF'; - - - htable.dump - - auth - - -EOF - - my $user_res = $dispatcher->dispatch($c, "loadbalancer", 1, 1, $user_xml); - my @users = (); - my $usr = {}; - for my $host (grep {$$_[1]} @$user_res) { - my $xmlDoc = $xml_parser->parse_string($host->[2]); - my $username = ''; - my $key = ''; - foreach my $node ($xmlDoc->findnodes('//member')) { - my $name = $node->findvalue('./name'); - my $value = $node->findvalue('./value/string') || - $node->findvalue('./value/int'); - if ($name eq 'name') { - $value =~ m/(?.*)::(?.*)/; - $username = $+{user}; - $key = $+{key}; - } elsif ($name eq 'value' && $username && $key) { - # there souldn't be any other keys - $key eq 'auth_count' and $usr->{$username}->{auth_count} = $value; - $key eq 'last_auth' and $usr->{$username}->{last_auth} = $value; - } - } - } - - for my $key (keys %{ $usr }) { - push @users, { - username => $key, - auth_count => $usr->{$key}->{auth_count}, - last_auth => NGCP::Panel::Utils::DateTime::epoch_local($usr->{$key}->{last_auth}), - } if($usr->{$key}->{auth_count} >= $c->config->{security}->{failed_auth_attempts}); - } + my $ips = NGCP::Panel::Utils::Security::list_banned_ips($c); + my $users = NGCP::Panel::Utils::Security::list_banned_users($c); $c->stash( template => 'security/list.tt', - banned_ips => \@ips, - banned_users => \@users, + banned_ips => $ips, + banned_users => $users, ); } @@ -102,21 +40,8 @@ sub ip_base :Chained('/') :PathPart('security/ip') :CaptureArgs(1) { sub ip_unban :Chained('ip_base') :PathPart('unban') :Args(0) { my ( $self, $c ) = @_; - my $dispatcher = NGCP::Panel::Utils::XMLDispatcher->new; my $ip = $c->stash->{ip}; - - my $xml = <<"EOF"; - - - htable.delete - - ipban - $ip - - -EOF - - $dispatcher->dispatch($c, "loadbalancer", 1, 1, $xml); + NGCP::Panel::Utils::Security::ip_unban($c, $ip); NGCP::Panel::Utils::Message::info( c => $c, data => { ip => $ip }, @@ -133,24 +58,8 @@ sub user_base :Chained('/') :PathPart('security/user') :CaptureArgs(1) { sub user_unban :Chained('user_base') :PathPart('unban') :Args(0) { my ( $self, $c ) = @_; - my $dispatcher = NGCP::Panel::Utils::XMLDispatcher->new; my $user = $c->stash->{user}; - - my @keys = ($user.'::auth_count', $user.'::last_auth'); - foreach my $key (@keys) { - my $xml = <<"EOF"; - - - htable.delete - - auth - $key - - -EOF - - $dispatcher->dispatch($c, "loadbalancer", 1, 1, $xml); - } + NGCP::Panel::Utils::Security::ip_unban($c, $user); NGCP::Panel::Utils::Message::info( c => $c, data => { user => $user }, diff --git a/lib/NGCP/Panel/Role/API/BannedIps.pm b/lib/NGCP/Panel/Role/API/BannedIps.pm new file mode 100644 index 0000000000..8011c4bd07 --- /dev/null +++ b/lib/NGCP/Panel/Role/API/BannedIps.pm @@ -0,0 +1,38 @@ +package NGCP::Panel::Role::API::BannedIps; + +use Sipwise::Base; + + +use parent qw/NGCP::Panel::Role::API/; + +use NGCP::Panel::Utils::Generic qw(:all); +use boolean qw(true); +use HTTP::Status qw(:constants); +use NGCP::Panel::Form::Peering::Group; +use NGCP::Panel::Utils::Peering; + +sub item_name { + return 'bannedips'; +} + +sub resource_name{ + return 'bannedips'; +} + +sub get_item_id{ + my($self, $c, $item, $resource, $form) = @_; + return $item->{ip}; +} + +sub valid_id { + my ($self, $c, $id) = @_; + return 1 if $id=~/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}(?:\/\d{1,2})?$/; + $self->error($c, HTTP_BAD_REQUEST, "Invalid id in request URI. Should be an ip address."); + return; +} +sub item_by_id{ + my ($self, $c, $id) = @_; + return $id; +} +1; +# vim: set tabstop=4 expandtab: diff --git a/lib/NGCP/Panel/Role/API/BannedUsers.pm b/lib/NGCP/Panel/Role/API/BannedUsers.pm new file mode 100644 index 0000000000..5c4079ca7f --- /dev/null +++ b/lib/NGCP/Panel/Role/API/BannedUsers.pm @@ -0,0 +1,41 @@ +package NGCP::Panel::Role::API::BannedUsers; + +use Sipwise::Base; + + +use parent qw/NGCP::Panel::Role::API/; + +use NGCP::Panel::Utils::Generic qw(:all); +use boolean qw(true); +use HTTP::Status qw(:constants); +use NGCP::Panel::Form::Peering::Group; +use NGCP::Panel::Utils::Peering; + +sub item_name { + return 'bannedusers'; +} + +sub resource_name{ + return 'bannedusers'; +} + +sub get_item_id{ + my($self, $c, $item, $resource, $form) = @_; + return $item->{username}; +} + +sub valid_id { + my ($self, $c, $id) = @_; + return 1 if $id=~/^[^@]+@[^@]+$/; + $self->error($c, HTTP_BAD_REQUEST, "Invalid id in request URI. Should be an ip address."); + return; +} + +sub item_by_id{ + my ($self, $c, $id) = @_; + return $id; +} + + +1; +# vim: set tabstop=4 expandtab: diff --git a/lib/NGCP/Panel/Utils/Security.pm b/lib/NGCP/Panel/Utils/Security.pm new file mode 100644 index 0000000000..b2645a5c7e --- /dev/null +++ b/lib/NGCP/Panel/Utils/Security.pm @@ -0,0 +1,138 @@ +package NGCP::Panel::Utils::Security; +use Sipwise::Base; + +use XML::LibXML; +use URI::Encode; +use NGCP::Panel::Utils::XMLDispatcher; +use NGCP::Panel::Utils::DateTime; + +sub list_banned_ips { + my ( $c ) = @_; + my $dispatcher = NGCP::Panel::Utils::XMLDispatcher->new; + my $xml_parser = XML::LibXML->new(); + + my $ip_xml = <<'EOF'; + + + htable.dump + + ipban + + +EOF + + my $ip_res = $dispatcher->dispatch($c, "loadbalancer", 1, 1, $ip_xml); + + my @ips = (); + for my $host (grep {$$_[1]} @$ip_res) { + my $xmlDoc = $xml_parser->parse_string($host->[2]); + foreach my $node ($xmlDoc->findnodes('//member')) { + my $name = $node->findvalue('./name'); + my $value = $node->findvalue('./value/string'); + if ($name eq 'name') { + push @ips, { ip => $value }; + } + } + } + return \@ips; +} + +sub list_banned_users { + my ( $c, %params ) = @_; + + my $dispatcher = NGCP::Panel::Utils::XMLDispatcher->new; + my $xml_parser = XML::LibXML->new(); + + my $user_xml = <<'EOF'; + + + htable.dump + + auth + + +EOF + + my $user_res = $dispatcher->dispatch($c, "loadbalancer", 1, 1, $user_xml); + my @users = (); + my $usr = {}; + for my $host (grep {$$_[1]} @$user_res) { + my $xmlDoc = $xml_parser->parse_string($host->[2]); + my $username = ''; + my $key = ''; + foreach my $node ($xmlDoc->findnodes('//member')) { + my $name = $node->findvalue('./name'); + my $value = $node->findvalue('./value/string') || + $node->findvalue('./value/int'); + if ($name eq 'name') { + $value =~ m/(?.*)::(?.*)/; + $username = $+{user}; + $key = $+{key}; + } elsif ($name eq 'value' && $username && $key) { + # there souldn't be any other keys + $key eq 'auth_count' and $usr->{$username}->{auth_count} = $value; + $key eq 'last_auth' and $usr->{$username}->{last_auth} = $value; + } + } + } + + for my $key (keys %{ $usr }) { + my $last_auth = $usr->{$key}->{last_auth} ? NGCP::Panel::Utils::DateTime::epoch_local($usr->{$key}->{last_auth}) : undef; + if($last_auth && $params{data_for_json}){ + $last_auth = $last_auth->ymd.' '. $last_auth->hms; + } + push @users, { + username => $key, + auth_count => $usr->{$key}->{auth_count}, + last_auth => $last_auth, + } if($usr->{$key}->{auth_count} >= $c->config->{security}->{failed_auth_attempts}); + } + return \@users; +} + +sub ip_unban { + my ( $c, $ip ) = @_; + my $decoder = URI::Encode->new; + $ip = $decoder->decode($ip); + my $dispatcher = NGCP::Panel::Utils::XMLDispatcher->new; + + my $xml = <<"EOF"; + + + htable.delete + + ipban + $ip + + +EOF + + $dispatcher->dispatch($c, "loadbalancer", 1, 1, $xml); +} + +sub user_unban { + my ( $c, $user ) = @_; + my $decoder = URI::Encode->new; + $user = $decoder->decode($user); + my $dispatcher = NGCP::Panel::Utils::XMLDispatcher->new; + + my @keys = ($user.'::auth_count', $user.'::last_auth'); + foreach my $key (@keys) { + my $xml = <<"EOF"; + + + htable.delete + + auth + $key + + +EOF + + $dispatcher->dispatch($c, "loadbalancer", 1, 1, $xml); + } +} + +1; + +# vim: set tabstop=4 expandtab: diff --git a/t/api-rest/api-bannedips.t b/t/api-rest/api-bannedips.t new file mode 100644 index 0000000000..88191ddb3e --- /dev/null +++ b/t/api-rest/api-bannedips.t @@ -0,0 +1,51 @@ +use strict; + +use Test::Collection; +use Test::FakeData; +use Test::More; +use Data::Dumper; + +#use NGCP::Panel::Utils::Subscriber; + +my $test_machine = Test::Collection->new( + name => 'bannedips', +); +my $fake_data = Test::FakeData->new; + +$test_machine->methods->{collection}->{allowed} = {map {$_ => 1} qw(GET HEAD OPTIONS)}; +$test_machine->methods->{item}->{allowed} = {map {$_ => 1} qw(GET HEAD OPTIONS DELETE)}; + +$fake_data->set_data_from_script({ + 'bannedips' => { + 'data' => { + }, + }, +}); + +$test_machine->DATA_ITEM_STORE($fake_data->process('bannedips')); +$test_machine->ALLOW_EMPTY_COLLECTION(1); +$test_machine->form_data_item(); + +my $time = time(); + +my $hal_before = $test_machine->get_item_hal(undef,undef,1); + +my @ips = qw/127.0.0.1 127.0.0.2 127.0.0.3/; +foreach (@ips){ + `ngcp-sercmd lb htable.sets ipban $_ 1`; +} + +$test_machine->check_bundle(); + +if(!$test_machine->IS_EMPTY_COLLECTION){ + $test_machine->clear_test_data_all([map {"/api/bannedips/$_"} @ips]); +} +if(!$hal_before || $hal_before->{content_collection}->{total_count} < 1){ + my $hal_after = $test_machine->get_item_hal(undef,undef,1); + is($hal_after, undef, "Check that all added banned ips were deleted"); +} +#fake data aren't registered in this test machine, so they will stay. +done_testing; + + +# vim: set tabstop=4 expandtab: diff --git a/t/api-rest/api-bannedusers.t b/t/api-rest/api-bannedusers.t new file mode 100644 index 0000000000..6cedb5eb19 --- /dev/null +++ b/t/api-rest/api-bannedusers.t @@ -0,0 +1,54 @@ +use strict; + +use Test::Collection; +use Test::FakeData; +use Test::More; +use Data::Dumper; + +#use NGCP::Panel::Utils::Subscriber; + +my $test_machine = Test::Collection->new( + name => 'bannedusers', +); +my $fake_data = Test::FakeData->new; + +$test_machine->methods->{collection}->{allowed} = {map {$_ => 1} qw(GET HEAD OPTIONS)}; +$test_machine->methods->{item}->{allowed} = {map {$_ => 1} qw(GET HEAD OPTIONS DELETE)}; + +$fake_data->set_data_from_script({ + 'bannedusers' => { + 'data' => { + }, + }, +}); + +$test_machine->DATA_ITEM_STORE($fake_data->process('bannedusers')); +$test_machine->ALLOW_EMPTY_COLLECTION(1); +$test_machine->form_data_item(); + +my $hal_before = $test_machine->get_item_hal(undef,undef,1); + +my $time = time(); +my @users = qw/user1 user2 user3/; +foreach (@users){ + my $cmd1 = "ngcp-sercmd lb htable.sets auth $_\@domain.com::auth_count 10"; + my $cmd2 = "ngcp-sercmd lb htable.sets auth $_\@domain.com::last_auth $time"; + print $cmd1."\n".$cmd2."\n"; + `$cmd1`; + `$cmd2`; +} + +$test_machine->check_bundle(); +print "is_empty:".$test_machine->IS_EMPTY_COLLECTION.";"; +if(!$test_machine->IS_EMPTY_COLLECTION){ + $test_machine->clear_test_data_all([map {"/api/bannedusers/$_\@domain.com"} @users]); +} +if(!$hal_before || $hal_before->{content_collection}->{total_count} < 1){ + my $hal_after = $test_machine->get_item_hal(undef,undef,1); + print Dumper $hal_after; + is($hal_after, undef, "Check that all added banned users were deleted"); +} +done_testing; + + +# vim: set tabstop=4 expandtab: diff --git a/t/lib/Test/Collection.pm b/t/lib/Test/Collection.pm index 7d9ae63f28..a938943d4f 100644 --- a/t/lib/Test/Collection.pm +++ b/t/lib/Test/Collection.pm @@ -334,10 +334,10 @@ sub get_uri_item{ return $resuri; } sub get_item_hal{ - my($self,$name,$uri) = @_; + my($self,$name,$uri, $reload) = @_; $name ||= $self->name; my $resitem ; - if(!$uri){ + if(!$uri && !$reload){ if(( $name eq $self->name ) && $self->DATA_CREATED->{FIRST}){ $resitem = $self->get_created_first; } @@ -346,14 +346,22 @@ sub get_item_hal{ } } if(!$resitem){ - my ($reshal, $location,$total_count); + my ($reshal, $location,$total_count,$reshal_collection); $uri //= $self->get_uri_collection($name)."?page=1&rows=1"; #print "uri=$uri;"; my($res,$list_collection,$req) = $self->check_item_get($self->normalize_uri($uri)); - ($reshal,$location,$total_count) = $self->get_hal_from_collection($list_collection,$name); + ($reshal,$location,$total_count,$reshal_collection) = $self->get_hal_from_collection($list_collection,$name); if($total_count || ('HASH' eq ref $reshal->{content} && $reshal->{content}->{total_count})){ $self->IS_EMPTY_COLLECTION(0); - $resitem = { num => 1, content => $reshal, res => $res, req => $req, location => $location, total_count => $total_count }; + $resitem = { + num => 1, + content => $reshal, + res => $res, + req => $req, + location => $location, + total_count => $total_count, + content_collection => $reshal_collection, + }; $self->DATA_LOADED->{$name} ||= []; push @{$self->DATA_LOADED->{$name}}, $resitem; }else{ @@ -366,7 +374,8 @@ sub get_hal_from_collection{ my($self,$list_collection,$name) = @_; $name ||= $self->name; my $hal_name = $self->get_hal_name($name); - my($reshal,$location,$total_count); + my($reshal,$reshal_collection,$location,$total_count); + $reshal_collection = $list_collection; if( $list_collection->{_embedded} && ref $list_collection->{_embedded}->{$hal_name} eq 'ARRAY') { $reshal = $list_collection->{_embedded}->{$hal_name}->[0]; $location = $reshal->{_links}->{self}->{href}; @@ -379,14 +388,14 @@ sub get_hal_from_collection{ #found first subscriber $reshal = $list_collection; $location = $reshal->{_links}->{$hal_name}->{href}; - $total_count = $reshal->{total_count}; + $total_count = $reshal->{total_count}; } elsif( ref $list_collection eq 'HASH' && $list_collection->{_links}->{self}->{href}) { #preferencedefs collection $reshal = $list_collection; $location = $reshal->{_links}->{self}->{href}; $total_count = $reshal->{total_count}; } - return ($reshal,$location,$total_count); + return ($reshal,$location,$total_count,$reshal_collection); } sub get_created_first{ my($self) = @_;