TT#49037 Implement rds connector to sipwise eds

Change-Id: Ib10c44dce69ce7a1f7c1017ce25b85c6590f9b0c
changes/56/25756/18
Andreas Granig 6 years ago
parent 759ce4e9ab
commit ea25662c18

@ -1610,6 +1610,8 @@ sub pbx_device_create :Chained('base') :PathPart('pbx/device/create') :Args(0) {
$c, 'register', $fdev);
unless($err) {
$err = $c->forward('pbx_device_lines_update', [$schema, $fdev, [$form->field('line')->fields]]);
} else {
die $err;
}
});

@ -255,11 +255,13 @@ has_field 'bootstrap_method' => (
{ label => 'Polycom', value => 'redirect_polycom' },
{ label => 'Snom', value => 'redirect_snom' },
{ label => 'Grandstream', value => 'redirect_grandstream' },
{ label => 'Sipwise Redirect', value => 'redirect_sipwise' },
{ label => 'Sipwise Profile', value => 'profile_sipwise' },
],
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.'],
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.'],
javascript => ' onchange="bootstrapDynamicFields(this.options[this.selectedIndex].value);" ',
},
);
@ -438,6 +440,52 @@ has_field 'bootstrap_config_redirect_grandstream_key' => (
},
);
has_field 'bootstrap_config_redirect_sipwise_user' => (
type => 'Text',
required => 0,
label => 'Sipwise EDS username',
default => '',
wrapper_class => [qw/ngcp-devicetype ngcp-devicetype-phone ngcp-bootstrap-config ngcp-bootstrap-config-redirect_sipwise/],
element_attr => {
rel => ['tooltip'],
title => ['Username used to configure device on Sipwise EDS.'],
},
);
has_field 'bootstrap_config_redirect_sipwise_password' => (
type => 'Text',
required => 0,
label => 'Sipwise EDS password',
default => '',
wrapper_class => [qw/ngcp-devicetype ngcp-devicetype-phone ngcp-bootstrap-config ngcp-bootstrap-config-redirect_sipwise/],
element_attr => {
rel => ['tooltip'],
title => ['Password used to configure device on Sipwise EDS.'],
},
);
has_field 'bootstrap_config_profile_sipwise_user' => (
type => 'Text',
required => 0,
label => 'Sipwise EDS username',
default => '',
wrapper_class => [qw/ngcp-devicetype ngcp-devicetype-phone ngcp-bootstrap-config ngcp-bootstrap-config-profile_sipwise/],
element_attr => {
rel => ['tooltip'],
title => ['Username used to configure device on Sipwise EDS.'],
},
);
has_field 'bootstrap_config_profile_sipwise_password' => (
type => 'Text',
required => 0,
label => 'Sipwise EDS password',
default => '',
wrapper_class => [qw/ngcp-devicetype ngcp-devicetype-phone ngcp-bootstrap-config ngcp-bootstrap-config-profile_sipwise/],
element_attr => {
rel => ['tooltip'],
title => ['Password used to configure device on Sipwise EDS.'],
},
);
has_field 'save' => (
type => 'Submit',
value => 'Save',
@ -448,7 +496,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 front_image 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 front_image mac_image/],
);
has_block 'actions' => (

@ -104,6 +104,13 @@ sub get_server_cert {
return $cert_file->slurp;
}
sub get_server_ca_cert {
my ($self, $c) = @_;
my $cert_file = Path::Tiny->new($c->config->{ssl}->{server_cacertfile});
return $cert_file->slurp;
}
sub get_provisioning_server_cert {
my ($self, $c) = @_;

@ -21,6 +21,9 @@ sub store_and_process_device_model {
my $sync_parameters = NGCP::Panel::Utils::DeviceBootstrap::devmod_sync_parameters_prefetch($c, $item, $resource);
my $credentials = NGCP::Panel::Utils::DeviceBootstrap::devmod_sync_credentials_prefetch($c, $item, $resource);
NGCP::Panel::Utils::DeviceBootstrap::devmod_sync_clear($c, $resource);
# TODO: DB errors thrown in the actions below are not caught and
# produce a 500 error without any logs, which makes it really=
# difficult to find the reason
if($item){
$item->update($resource);
$c->model('DB')->resultset('autoprov_sync')->search_rs({

@ -11,6 +11,8 @@ use NGCP::Panel::Utils::DeviceBootstrap::Yealink;
use NGCP::Panel::Utils::DeviceBootstrap::Polycom;
use NGCP::Panel::Utils::DeviceBootstrap::Snom;
use NGCP::Panel::Utils::DeviceBootstrap::Grandstream;
use NGCP::Panel::Utils::DeviceBootstrap::SipwiseRedirect;
use NGCP::Panel::Utils::DeviceBootstrap::SipwiseProfile;
my $redirect_processor;
@ -108,6 +110,10 @@ sub get_redirect_processor{
$redirect_processor = NGCP::Panel::Utils::DeviceBootstrap::Snom->new( params => $params );
}elsif('redirect_grandstream' eq $bootstrap_method){
$redirect_processor = NGCP::Panel::Utils::DeviceBootstrap::Grandstream->new( params => $params );
}elsif('redirect_sipwise' eq $bootstrap_method){
$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('http' eq $bootstrap_method){
#$ret = panasonic_bootstrap_register($params);
}

@ -0,0 +1,189 @@
package NGCP::Panel::Utils::DeviceBootstrap::SipwiseProfile;
use strict;
use warnings;
use URI::Escape;
use Moo;
use Types::Standard qw(Str);
use JSON qw/encode_json decode_json/;
use MIME::Base64;
extends 'NGCP::Panel::Utils::DeviceBootstrap::VendorREST';
sub rpc_server_params {
my $self = shift;
my $cfg = {
proto => 'https',
host => 'api.eds.sipwise.com',
port => '443',
path => 'api',
};
$self->{rpc_server_params} = $cfg;
return $self->{rpc_server_params};
}
sub register_model {
my($self) = @_;
$self->rpc_server_params;
my $c = $self->params->{c};
$c->log->error("++++ SipwiseProfile register_model");
my $cfg = $self->{rpc_server_params};
my $redirect_url = $self->get_config_uri();
# first, fetch profile from eds
my $url = "$$cfg{proto}://$$cfg{host}:$$cfg{port}/$$cfg{path}/profiles?q=ngcp";
$c->log->debug("SipwiseProfile check profile '$url'");
my $req = HTTP::Request->new(GET => $url);
$req->header(%{$self->get_basic_authorization($self->params->{credentials})});
my $res = $self->_ua->request($req);
my $prof;
if ($res->is_success) {
$c->log->debug("SipwiseProfile check profile query successful, data: " . $res->decoded_content);
my $data = decode_json($res->decoded_content);
if (ref $data eq 'HASH' && exists $data->{data} && ref $data->{data} eq 'ARRAY' && @{ $data->{data} }) {
# profile exists, nothing to do
$prof = shift @{ $data->{data} };
} elsif (ref $data eq 'HASH' && exists $data->{data} && ref $data->{data} eq 'ARRAY' && @{ $data->{data} } == 0) {
# profile does not exist, create it
$c->log->debug("SipwiseProfile ngcp profile not available for this reseller, create it");
# first we need to create the blob containing the
# server's ca cert
my $cert = $c->model('CA')->get_server_ca_cert($c);
$c->log->debug("SipwiseProfile got ca cert, encode");
my $cacert = encode_base64($cert);
$c->log->debug("SipwiseProfile got encoded ca cert, send");
$url = "$$cfg{proto}://$$cfg{host}:$$cfg{port}/$$cfg{path}/blobs";
$c->log->debug("SipwiseProfile create blob '$url'");
my $req = HTTP::Request->new(POST => $url);
$req->header(%{$self->get_basic_authorization($self->params->{credentials})});
$req->content(encode_json({ data => { name => 'ngcp-ca-cert.pem', b64body => $cacert, content_type => 'application/octet-stream' } }));
$req->content_type('application/json');
$res = $self->_ua->request($req);
if ($res->is_success) {
$c->log->debug("SipwiseProfile create blob query successful");
} else {
$c->log->error("SipwiseProfile create blob query failed (" . $res->status_line . "): " . $res->decoded_content);
return;
}
# now create the profile, referring to the blob and the
# server's provisioning url
my $profile_body = "<settings><setting override=\"true\" value=\"https:\/\/dev.eds.sipwise.com\/dev\/blob\/\$EDS{MAC}\/ngcp-ca-cert.pem\" id=\"ThirdPartyCAUrl\"\/><setting override=\"true\" value=\"$redirect_url\" id=\"EdsEnetcfgDmUrl\"\/><\/settings>";
$url = "$$cfg{proto}://$$cfg{host}:$$cfg{port}/$$cfg{path}/profiles";
$c->log->debug("SipwiseProfile create profile '$url'");
$req = HTTP::Request->new(POST => $url);
$req->header(%{$self->get_basic_authorization($self->params->{credentials})});
$req->content(encode_json({ data => { body => $profile_body, content_type => 'text/xml', description => 'ngcp' } }));
$req->content_type('application/json');
$res = $self->_ua->request($req);
if ($res->is_success) {
$c->log->debug("SipwiseProfile create profile query successful, data: " . $res->decoded_content);
$data = decode_json($res->decoded_content);
if (ref $data eq 'HASH' && exists $data->{data} && ref $data->{data} eq 'HASH') {
$prof = $data->{data};
} else {
$c->log->error("SipwiseProfile create profile query failed with invalid data: " . $res->decoded_content);
return;
}
} else {
$c->log->error("SipwiseProfile create profile query failed (" . $res->status_line . "): " . $res->decoded_content);
return;
}
} else {
$c->log->error("SipwiseProfile check profile query failed due to invalid body");
return;
}
} else {
$c->log->error("SipwiseProfile fetch profile query failed (" . $res->status_line . "): " . $res->decoded_content);
return;
}
return 1;
}
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};
$self->{rpc_server_params} //= $self->rpc_server_params;
my $cfg = $self->{rpc_server_params};
$c->log->debug("SipwiseProfile prepare request for action $action");
if ($action eq 'register_content') {
# first, fetch profile from eds
my $url = "$$cfg{proto}://$$cfg{host}:$$cfg{port}/$$cfg{path}/profiles?q=ngcp";
$c->log->debug("SipwiseProfile check profile '$url'");
my $req = HTTP::Request->new(GET => $url);
$req->header(%{$self->get_basic_authorization($self->params->{credentials})});
my $res = $self->_ua->request($req);
my $prof;
if ($res->is_success) {
$c->log->debug("SipwiseProfile check profile query successful, data: " . $res->decoded_content);
my $data = decode_json($res->decoded_content);
if (ref $data eq 'HASH' && exists $data->{data} && ref $data->{data} eq 'ARRAY' && @{ $data->{data} }) {
# profile exists, nothing to do
$prof = shift @{ $data->{data} };
} elsif (ref $data eq 'HASH' && exists $data->{data} && ref $data->{data} eq 'ARRAY' && @{ $data->{data} } == 0) {
$c->log->error("SipwiseProfile ngcp profile does not exist");
return;
} else {
$c->log->error("SipwiseProfile check profile query failed due to invalid body");
return;
}
$ret = {
method =>'POST',
url => "$$cfg{proto}://$$cfg{host}:$$cfg{port}/$$cfg{path}/devices",
body => { data => { mac => $new_mac, profile_id => $prof->{id}, url => undef} },
};
} else {
$c->log->error("SipwiseProfile unregister 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
my $url = "$$cfg{proto}://$$cfg{host}:$$cfg{port}/$$cfg{path}/devices?q=$old_mac";
$c->log->debug("SipwiseProfile unregister via url '$url'");
my $req = HTTP::Request->new(GET => $url);
$req->header(%{$self->get_basic_authorization($self->params->{credentials})});
my $res = $self->_ua->request($req);
if ($res->is_success) {
$c->log->debug("SipwiseProfile unregister query successful, data: " . $res->decoded_content);
my $data = decode_json($res->decoded_content);
my $dev;
if (ref $data eq 'HASH' && exists $data->{data} && ref $data->{data} eq 'ARRAY' && @{ $data->{data} }) {
$dev = shift @{ $data->{data} };
} else {
$c->log->error("SipwiseProfile unregister query failed due to invalid body");
return;
}
$ret = {
method =>'DELETE',
url => "$$cfg{proto}://$$cfg{host}:$$cfg{port}/$$cfg{path}/devices/$$dev{id}",
body => undef,
};
} else {
$c->log->error("SipwiseProfile unregister query failed (" . $res->status_line . "): " . $res->decoded_content);
return;
}
}
return $ret;
}
1;
# vim: set tabstop=4 expandtab:

@ -0,0 +1,76 @@
package NGCP::Panel::Utils::DeviceBootstrap::SipwiseRedirect;
use strict;
use warnings;
use URI::Escape;
use Moo;
use Types::Standard qw(Str);
use JSON qw/encode_json decode_json/;
extends 'NGCP::Panel::Utils::DeviceBootstrap::VendorREST';
sub rpc_server_params {
my $self = shift;
my $cfg = {
proto => 'https',
host => 'api.eds.sipwise.com',
port => '443',
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 $redirect_url = $self->get_bootstrap_uri();
$self->{rpc_server_params} //= $self->rpc_server_params;
my $cfg = $self->{rpc_server_params};
if ($action eq 'register_content') {
$ret = {
method =>'POST',
url => "$$cfg{proto}://$$cfg{host}:$$cfg{port}/$$cfg{path}/devices",
body => { data => { mac => $new_mac, profile_id => undef, url => $redirect_url} },
};
} elsif ($action eq 'unregister_content') {
# we've to fetch the id first before constructing the delete request
my $url = "$$cfg{proto}://$$cfg{host}:$$cfg{port}/$$cfg{path}/devices?q=$old_mac";
$c->log->debug("SipwiseRedirect unregister via url '$url'");
my $req = HTTP::Request->new(GET => $url);
$req->header(%{$self->get_basic_authorization($self->params->{credentials})});
my $res = $self->_ua->request($req);
if ($res->is_success) {
$c->log->debug("SipwiseRedirect unregister query successful, data: " . $res->decoded_content);
my $data = decode_json($res->decoded_content);
my $dev;
if (ref $data eq 'HASH' && exists $data->{data} && ref $data->{data} eq 'ARRAY' && @{ $data->{data} }) {
$dev = shift @{ $data->{data} };
} else {
$c->log->error("SipwiseRedirect unregister query failed due to invalid body");
return;
}
$ret = {
method =>'DELETE',
url => "$$cfg{proto}://$$cfg{host}:$$cfg{port}/$$cfg{path}/devices/$$dev{id}",
body => undef,
};
} else {
$c->log->error("SipwiseRedirect unregister query failed (" . $res->status_line . "): " . $res->decoded_content);
return;
}
}
return $ret;
}
1;
# vim: set tabstop=4 expandtab:

@ -0,0 +1,43 @@
package NGCP::Panel::Utils::DeviceBootstrap::VendorREST;
use strict;
use warnings;
use Moo;
use HTTP::Request;
use JSON qw/encode_json decode_json/;
extends 'NGCP::Panel::Utils::DeviceBootstrap::VendorRPC';
sub redirect_server_call {
my ($self, $action) = @_;
my $c = $self->params->{c};
$self->init_content_params();
$c->log->debug("Performing VendorREST call for action '$action'");
my $method = $action.'_content';
my $data = $self->rest_prepare_request($method);
unless ($data) {
$c->log->debug("VendorREST call for action '$action' failed due to missing data");
return "No bootstrap data from redirect service";
}
my $req = HTTP::Request->new($data->{method} => $data->{url});
$req->header(%{$self->get_basic_authorization($self->params->{credentials})});
if (defined $data->{body}) {
$req->content(encode_json($data->{body}));
$req->content_type('application/json');
}
my $res = $self->_ua->request($req);
if ($res->is_success) {
$c->log->info("Performing VendorREST for action '$action' succeeded");
return 0;
} else {
$c->log->error("Performing VendorREST for action '$action' failed (" . $res->status_line . "): " . $res->decoded_content);
return "Failed to perform redirect service action";
}
}
1;
# vim: set tabstop=4 expandtab:

@ -38,6 +38,23 @@ has 'unregister_content' => (
accessor => '_unregister_content',
);
has '_ua' => (
is => 'ro',
lazy => 1,
default => sub {
my $self = shift;
my $cfg = $self->rpc_server_params;
my $ua = LWP::UserAgent->new(keep_alive => 1);
$ua->ssl_opts(
verify_hostname => 0,
SSL_verify_mode => 0,
);
return $ua;
}
);
sub redirect_server_call{
my ($self, $action) = @_;
my $c = $self->params->{c};
@ -67,12 +84,7 @@ sub rpc_https_call{
eval {
local $SIG{ALRM} = sub { die "Connection timeout\n" };
alarm(25);
my $ua = LWP::UserAgent->new;
$ua->credentials($cfg->{host}.':'.$cfg->{port}, $cfg->{realm} // '', @$cfg{qw/user password/});
$ua->ssl_opts(
verify_hostname => 0,
SSL_verify_mode => 0,
);
my $ua = $self->_ua;
$cfg->{port} //= '';
my $uri = $cfg->{proto}.'://'.$cfg->{host}.($cfg->{port} ? ':' : '').$cfg->{port}.$cfg->{path};
my $request = POST $uri,
@ -147,6 +159,18 @@ sub get_basic_authorization{
return { 'Authorization' => 'Basic '.$authorization };
}
sub get_config_uri{
my ($self) = @_;
my $uri = $self->params->{redirect_uri};
my $uri_params = $self->params->{redirect_params}->{sync_params} || '';
if(!$uri){
my $cfg = $self->config_uri_conf();
$uri = "$cfg->{schema}://$cfg->{host}:$cfg->{port}/device/autoprov/config/";
}
$uri .= $uri_params;
return $self->process_bootstrap_uri($uri);
}
sub get_bootstrap_uri{
my ($self) = @_;
my $uri = $self->params->{redirect_uri};
@ -196,6 +220,17 @@ sub bootstrap_uri_conf{
return $cfg;
}
sub config_uri_conf{
my ($self) = @_;
my $c = $self->params->{c};
my $cfg = {
schema => $c->config->{deviceprovisioning}->{secure} ? 'https' : 'http',
host => $c->config->{deviceprovisioning}->{host} // $c->req->uri->host,
port => $c->config->{deviceprovisioning}->{port} // 1444,
};
return $cfg;
}
sub unknown_error{
my ($self) = @_;
my $c = $self->params->{c};

Loading…
Cancel
Save