From 16365fb256cc2efe8477664fb6cdd843b7fdc0d0 Mon Sep 17 00:00:00 2001 From: Flaviu Mates Date: Thu, 12 Mar 2020 14:34:44 +0200 Subject: [PATCH] TT#76783 - Implemend ALE RPS devices auto provisioning * Implement checking/creation of server, profile and prepare request for device creation on ALE RPS * Prepare request for device deletion on ALE RPS * Changes in Panel to support provisioning via the ALE RPS Change-Id: I24b62c03b64c56fcbcabea71428d0b70b46706e6 --- lib/NGCP/Panel/Form/Device/Model.pm | 29 ++- lib/NGCP/Panel/Form/Device/ModelAdmin.pm | 2 +- lib/NGCP/Panel/Utils/DeviceBootstrap.pm | 3 + lib/NGCP/Panel/Utils/DeviceBootstrap/ALE.pm | 197 ++++++++++++++++++ .../Panel/Utils/DeviceBootstrap/VendorREST.pm | 7 +- 5 files changed, 233 insertions(+), 5 deletions(-) create mode 100644 lib/NGCP/Panel/Utils/DeviceBootstrap/ALE.pm diff --git a/lib/NGCP/Panel/Form/Device/Model.pm b/lib/NGCP/Panel/Form/Device/Model.pm index 487953cca4..6fae1acb26 100644 --- a/lib/NGCP/Panel/Form/Device/Model.pm +++ b/lib/NGCP/Panel/Form/Device/Model.pm @@ -269,11 +269,12 @@ has_field 'bootstrap_method' => ( { label => 'Grandstream', value => 'redirect_grandstream' }, { label => 'Sipwise Redirect', value => 'redirect_sipwise' }, { label => 'Sipwise Profile', value => 'profile_sipwise' }, + { label => 'ALE', value => 'redirect_ale' }, ], default => 'http', element_attr => { rel => ['tooltip'], - title => ['Method to configure the provisioning server on the phone. One of http, redirect_panasonic, redirect_yealink, redirect_polycom, redirect_snom, redirect_grandstream, redirect_sipwise, profile_sipwise.'], + title => ['Method to configure the provisioning server on the phone. One of http, redirect_panasonic, redirect_yealink, redirect_polycom, redirect_snom, redirect_grandstream, redirect_sipwise, profile_sipwise, redirect_ale.'], javascript => ' onchange="bootstrapDynamicFields(this.options[this.selectedIndex].value);" ', }, ); @@ -283,7 +284,7 @@ has_field 'bootstrap_uri' => ( label => 'Bootstrap URI', default => '', #we don't show this field for polycom, because polycom doesn't support now any possibility to configure provisioning url via API - wrapper_class => [qw/ngcp-devicetype ngcp-devicetype-phone ngcp-bootstrap-config ngcp-bootstrap-config-http ngcp-bootstrap-config-redirect_panasonic ngcp-bootstrap-config-redirect_yealink ngcp-bootstrap-config-redirect_snom ngcp-bootstrap-config-redirect_grandstream/], + wrapper_class => [qw/ngcp-devicetype ngcp-devicetype-phone ngcp-bootstrap-config ngcp-bootstrap-config-http ngcp-bootstrap-config-redirect_panasonic ngcp-bootstrap-config-redirect_yealink ngcp-bootstrap-config-redirect_snom ngcp-bootstrap-config-redirect_grandstream ngcp-bootstrap-config-redirect_ale/], element_attr => { rel => ['tooltip'], title => ['Custom provisioning server URI.'], @@ -497,6 +498,28 @@ has_field 'bootstrap_config_profile_sipwise_password' => ( title => ['Password used to configure device on Sipwise EDS.'], }, ); +has_field 'bootstrap_config_redirect_ale_user' => ( + type => 'Text', + required => 0, + label => 'ALE username', + default => '', + wrapper_class => [qw/ngcp-devicetype ngcp-devicetype-phone ngcp-bootstrap-config ngcp-bootstrap-config-redirect_ale/], + element_attr => { + rel => ['tooltip'], + title => ['Username used to configure bootstrap url on ALE redirect server. Obtained from ALE.'], + }, +); +has_field 'bootstrap_config_redirect_ale_password' => ( + type => 'Text', + required => 0, + label => 'ALE password', + default => '', + wrapper_class => [qw/ngcp-devicetype ngcp-devicetype-phone ngcp-bootstrap-config ngcp-bootstrap-config-redirect_ale/], + element_attr => { + rel => ['tooltip'], + title => ['Password used to configure bootstrap url on ALE redirect server. Obtained from ALE.'], + }, +); has_field 'save' => ( type => 'Submit', @@ -508,7 +531,7 @@ has_field 'save' => ( has_block 'fields' => ( tag => 'div', class => [qw/modal-body/], - render_list => [qw/vendor model type extensions_num connectable_models linerange linerange_add bootstrap_method bootstrap_uri bootstrap_config_http_sync_method bootstrap_config_http_sync_uri bootstrap_config_http_sync_params bootstrap_config_redirect_panasonic_user bootstrap_config_redirect_panasonic_password bootstrap_config_redirect_yealink_user bootstrap_config_redirect_yealink_password bootstrap_config_redirect_polycom_user bootstrap_config_redirect_polycom_password bootstrap_config_redirect_polycom_profile bootstrap_config_redirect_grandstream_cid bootstrap_config_redirect_grandstream_key bootstrap_config_redirect_sipwise_user bootstrap_config_redirect_sipwise_password bootstrap_config_profile_sipwise_user bootstrap_config_profile_sipwise_password front_image front_thumbnail mac_image/], + render_list => [qw/vendor model type extensions_num connectable_models linerange linerange_add bootstrap_method bootstrap_uri bootstrap_config_http_sync_method bootstrap_config_http_sync_uri bootstrap_config_http_sync_params bootstrap_config_redirect_panasonic_user bootstrap_config_redirect_panasonic_password bootstrap_config_redirect_yealink_user bootstrap_config_redirect_yealink_password bootstrap_config_redirect_polycom_user bootstrap_config_redirect_polycom_password bootstrap_config_redirect_polycom_profile bootstrap_config_redirect_grandstream_cid bootstrap_config_redirect_grandstream_key bootstrap_config_redirect_sipwise_user bootstrap_config_redirect_sipwise_password bootstrap_config_profile_sipwise_user bootstrap_config_profile_sipwise_password bootstrap_config_redirect_ale_user bootstrap_config_redirect_ale_password front_image front_thumbnail mac_image/], ); has_block 'actions' => ( diff --git a/lib/NGCP/Panel/Form/Device/ModelAdmin.pm b/lib/NGCP/Panel/Form/Device/ModelAdmin.pm index a31bedead2..e11dd552b0 100644 --- a/lib/NGCP/Panel/Form/Device/ModelAdmin.pm +++ b/lib/NGCP/Panel/Form/Device/ModelAdmin.pm @@ -29,7 +29,7 @@ has_field 'save' => ( has_block 'fields' => ( tag => 'div', class => [qw/modal-body/], - render_list => [qw/reseller vendor model type extensions_num connectable_models linerange linerange_add bootstrap_method bootstrap_uri bootstrap_config_http_sync_method bootstrap_config_http_sync_uri bootstrap_config_http_sync_params bootstrap_config_redirect_panasonic_user bootstrap_config_redirect_panasonic_password bootstrap_config_redirect_yealink_user bootstrap_config_redirect_yealink_password bootstrap_config_redirect_polycom_user bootstrap_config_redirect_polycom_password bootstrap_config_redirect_polycom_profile bootstrap_config_redirect_snom_user bootstrap_config_redirect_snom_password bootstrap_config_redirect_grandstream_cid bootstrap_config_redirect_grandstream_key front_image front_thumbnail mac_image/], + render_list => [qw/reseller vendor model type extensions_num connectable_models linerange linerange_add bootstrap_method bootstrap_uri bootstrap_config_http_sync_method bootstrap_config_http_sync_uri bootstrap_config_http_sync_params bootstrap_config_redirect_panasonic_user bootstrap_config_redirect_panasonic_password bootstrap_config_redirect_yealink_user bootstrap_config_redirect_yealink_password bootstrap_config_redirect_polycom_user bootstrap_config_redirect_polycom_password bootstrap_config_redirect_polycom_profile bootstrap_config_redirect_snom_user bootstrap_config_redirect_snom_password bootstrap_config_redirect_grandstream_cid bootstrap_config_redirect_grandstream_key bootstrap_config_redirect_ale_user bootstrap_config_redirect_ale_password front_image front_thumbnail mac_image/], ); has_block 'actions' => ( diff --git a/lib/NGCP/Panel/Utils/DeviceBootstrap.pm b/lib/NGCP/Panel/Utils/DeviceBootstrap.pm index fcde57208c..5293025de6 100644 --- a/lib/NGCP/Panel/Utils/DeviceBootstrap.pm +++ b/lib/NGCP/Panel/Utils/DeviceBootstrap.pm @@ -13,6 +13,7 @@ use NGCP::Panel::Utils::DeviceBootstrap::Snom; use NGCP::Panel::Utils::DeviceBootstrap::Grandstream; use NGCP::Panel::Utils::DeviceBootstrap::SipwiseRedirect; use NGCP::Panel::Utils::DeviceBootstrap::SipwiseProfile; +use NGCP::Panel::Utils::DeviceBootstrap::ALE; my $redirect_processor; @@ -116,6 +117,8 @@ sub get_redirect_processor{ $redirect_processor = NGCP::Panel::Utils::DeviceBootstrap::SipwiseRedirect->new( params => $params ); }elsif('profile_sipwise' eq $bootstrap_method){ $redirect_processor = NGCP::Panel::Utils::DeviceBootstrap::SipwiseProfile->new( params => $params ); + }elsif('redirect_ale' eq $bootstrap_method){ + $redirect_processor = NGCP::Panel::Utils::DeviceBootstrap::ALE->new( params => $params ); }elsif('http' eq $bootstrap_method){ #$ret = panasonic_bootstrap_register($params); } diff --git a/lib/NGCP/Panel/Utils/DeviceBootstrap/ALE.pm b/lib/NGCP/Panel/Utils/DeviceBootstrap/ALE.pm new file mode 100644 index 0000000000..8ef52615ce --- /dev/null +++ b/lib/NGCP/Panel/Utils/DeviceBootstrap/ALE.pm @@ -0,0 +1,197 @@ +package NGCP::Panel::Utils::DeviceBootstrap::ALE; + +use strict; +use warnings; + +use URI::Escape; +use Moo; +use Types::Standard qw(Str); +use JSON qw/encode_json decode_json/; +use MIME::Base64; +use Digest::MD5 qw/md5_hex/; + +extends 'NGCP::Panel::Utils::DeviceBootstrap::VendorREST'; + +sub rpc_server_params{ + my $self = shift; + my $cfg = { + proto => 'https', + host => 'api.rps.ce.al-enterprise.com', + path => 'api', + }; + $self->{rpc_server_params} = $cfg; + return $self->{rpc_server_params}; +} + +sub rest_prepare_request { + my ($self, $action) = @_; + my $c = $self->params->{c}; + my $ret; + my $new_mac = $self->content_params->{mac}; + my $old_mac = $self->content_params->{mac_old}; + my $param_servername = $self->content_params->{server_name}; + my $param_uri = $self->content_params->{uri}; + my $token; + + $self->{rpc_server_params} //= $self->rpc_server_params; + my $cfg = $self->{rpc_server_params}; + + $c->log->debug("ALE prepare request for action $action"); + + # first, generate token + my $url = "$$cfg{proto}://$$cfg{host}/$$cfg{path}/bp_user/generate/token"; + $c->log->debug("ALE generate token '$url'"); + my $req = HTTP::Request->new(GET => $url); + $req->header(':api_user_name' => $self->params->{credentials}->{user}); + $req->header(':api_password' => $self->params->{credentials}->{password}); + my $res = $self->_ua->request($req); + + my $data = decode_json($res->decoded_content); + if ($res->is_success && $data->{success}) { + $c->log->debug("Token generation successful, data: " . $res->decoded_content); + $token = $data->{data}->{token}; + } else { + $c->log->error("Token generation failed (" . $res->status_line . "): " . $res->decoded_content); + return; + } + + if ($action eq 'register_content') { + # fetch server + $url = "$$cfg{proto}://$$cfg{host}/$$cfg{path}/servers"; + $c->log->debug("ALE check server '$url'"); + $req = HTTP::Request->new(GET => $url); + $req->header(token => $token); + $res = $self->_ua->request($req); + + my $server_id; + $data = decode_json($res->decoded_content); + if ($res->is_success && $data->{success}) { + $c->log->debug("ALE check server query successful, data: " . $res->decoded_content); + my ($server) = grep {$_->{server_url} eq $param_uri} @{$data->{data}->{server_list}}; + if ($server) { + $server_id = $server->{server_id}; + } + else { + #server does not exist, create it + $c->log->debug("ALE create server '$url'"); + $req = HTTP::Request->new(POST => $url); + $req->header(token => $token); + $req->content_type('application/json'); + $req->content(encode_json({ + server_name => $param_servername, + server_url => $param_uri, + }) + ); + $res = $self->_ua->request($req); + $data = decode_json($res->decoded_content); + if ($res->is_success && $data->{success}) { + $c->log->debug("ALE create server query successful, data: " . $res->decoded_content); + $server_id = $data->{data}->{server_id}; + } + else{ + $c->log->error("ALE create server query failed (" . $res->status_line . "): " . $res->decoded_content); + return; + } + } + } else { + $c->log->error("ALE check server query failed (" . $res->status_line . "): " . $res->decoded_content); + return; + } + + # fetch profile + $url = "$$cfg{proto}://$$cfg{host}/$$cfg{path}/profiles"; + $c->log->debug("ALE check server '$url'"); + $req = HTTP::Request->new(GET => $url); + $req->header(token => $token); + $res = $self->_ua->request($req); + + $data = decode_json($res->decoded_content); + if ($res->is_success && $data->{success}) { + $c->log->debug("ALE check profile query successful, data: " . $res->decoded_content); + my $profile_id; + my ($profile) = grep {$_->{server_id} == $server_id} @{$data->{data}->{profile_list}}; + if ($profile) { + $profile_id = $profile->{profile_id}; + } + else { + #profile does not exist, create it + $c->log->debug("ALE create profile '$url'"); + $req = HTTP::Request->new(POST => $url); + $req->header(token => $token); + $req->content_type('application/json'); + $req->content(encode_json({ + profile_name => $param_servername, + server_id => $server_id, + }) + ); + $res = $self->_ua->request($req); + $data = decode_json($res->decoded_content); + if ($res->is_success && $data->{success}) { + $c->log->debug("ALE create profile query successful, data: " . $res->decoded_content); + $profile_id = $data->{data}->{profile_id}; + } + else{ + $c->log->error("ALE create profile query failed (" . $res->status_line . "): " . $res->decoded_content); + return; + } + } + + $ret = { + method =>'POST', + url => "$$cfg{proto}://$$cfg{host}/$$cfg{path}/devices", + body => { macs => [{mac =>$new_mac}], profile_id => $profile_id}, + token => $token, + }; + } else { + $c->log->error("ALE check profile query failed (" . $res->status_line . "): " . $res->decoded_content); + return; + } + } elsif ($action eq 'unregister_content') { + # we've to fetch the id first before constructing the delete request + $url = "$$cfg{proto}://$$cfg{host}/$$cfg{path}/devices"; + $c->log->debug("ALE check devices '$url'"); + $req = HTTP::Request->new(GET => $url); + $req->header(token => $token); + $res = $self->_ua->request($req); + $data = decode_json($res->decoded_content); + if ($res->is_success && $data->{success}) { + $c->log->debug("ALE check devices query successful, data: " . $res->decoded_content); + my $device_id; + my ($device) = grep {$_->{mac} == $old_mac} @{$data->{data}->{device_list}}; + if ($device) { + $device_id = $device->{device_id}; + } + $c->log->debug("ALE unregister query successful, data: " . $res->decoded_content); + $data = decode_json($res->decoded_content); + $ret = { + method =>'DELETE', + url => "$$cfg{proto}://$$cfg{host}/$$cfg{path}/devices/$device_id", + body => undef, + token => $token, + }; + } else { + $c->log->error("ALE unregister query failed (" . $res->status_line . "): " . $res->decoded_content); + return; + } + } + + return $ret; +} + +around 'process_bootstrap_uri' => sub { + my($orig_method, $self, $uri) = @_; + $uri = $self->$orig_method($uri); + $self->content_params->{uri} = $uri; + $self->bootstrap_uri_server_name($uri); + return $self->content_params->{uri}; +}; + +sub bootstrap_uri_server_name{ + my($self,$uri) = @_; + $uri ||= $self->content_params->{uri}; + #http://stackoverflow.com/questions/4826403/hash-algorithm-with-alphanumeric-output-of-20-characters-max + $self->content_params->{server_name} ||= substr(md5_hex($uri),0,20); + return $self->content_params->{server_name}; +} + +1; diff --git a/lib/NGCP/Panel/Utils/DeviceBootstrap/VendorREST.pm b/lib/NGCP/Panel/Utils/DeviceBootstrap/VendorREST.pm index dbd2280ec0..708ad3cbfa 100644 --- a/lib/NGCP/Panel/Utils/DeviceBootstrap/VendorREST.pm +++ b/lib/NGCP/Panel/Utils/DeviceBootstrap/VendorREST.pm @@ -23,7 +23,12 @@ sub redirect_server_call { } my $req = HTTP::Request->new($data->{method} => $data->{url}); - $req->header(%{$self->get_basic_authorization($self->params->{credentials})}); + if ($data->{token}) { + $req->header(token => $data->{token}); + } + else { + $req->header(%{$self->get_basic_authorization($self->params->{credentials})}); + } if (defined $data->{body}) { $req->content(encode_json($data->{body})); $req->content_type('application/json');