* Remove old XMLRPC redirection logic * Implement Hawk header generation for SRAPS authorization * Implement bootstraping provisioning profile on SRAPS, then add the device into said profile * Implement deletion of device * Add 'Profile' and 'Product family' fields in SNOM device models Change-Id: I44ecf5199a7c04c6b0cb2e969aaa7f75578d874cmr9.1
parent
6be18bb2e6
commit
5a020e3740
@ -1,130 +1,296 @@
|
||||
package NGCP::Panel::Utils::DeviceBootstrap::Snom;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use URI::Escape;
|
||||
use Moo;
|
||||
use Data::Dumper;
|
||||
extends 'NGCP::Panel::Utils::DeviceBootstrap::VendorRPC';
|
||||
use Types::Standard qw(Str);
|
||||
use JSON qw/encode_json decode_json/;
|
||||
use MIME::Base64;
|
||||
use Digest::MD5 qw/md5_hex/;
|
||||
use Digest::SHA qw(hmac_sha256_base64);
|
||||
use URI;
|
||||
|
||||
extends 'NGCP::Panel::Utils::DeviceBootstrap::VendorREST';
|
||||
|
||||
sub rpc_server_params{
|
||||
my $self = shift;
|
||||
my $cfg = {
|
||||
proto => 'https',
|
||||
host => 'secure-provisioning.snom.com',
|
||||
port => '8083',
|
||||
path => '/xmlrpc',
|
||||
path => 'api/v1',
|
||||
};
|
||||
$cfg->{headers} = { %{$self->get_basic_authorization($self->params->{credentials})} };
|
||||
$self->{rpc_server_params} = $cfg;
|
||||
return $self->{rpc_server_params};
|
||||
}
|
||||
|
||||
sub register_content {
|
||||
my $self = shift;
|
||||
|
||||
my $param_mac = $self->content_params->{mac};
|
||||
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_uri = $self->content_params->{uri};
|
||||
my $credentials = {
|
||||
id => $self->params->{credentials}->{user},
|
||||
key => $self->params->{credentials}->{password}
|
||||
};
|
||||
|
||||
#<param><value><string>".URI::Escape::uri_escape($self->content_params->{uri})."</string></value></param>
|
||||
#http://fox.snom.com/prv2.php?mac={mac}
|
||||
|
||||
$self->{register_content} = <<EOS_XML;
|
||||
<?xml version='1.0'?>
|
||||
<methodCall>
|
||||
<methodName>redirect.registerPhone</methodName>
|
||||
<params>
|
||||
<param>
|
||||
<value><string>$param_mac</string></value>
|
||||
</param>
|
||||
<param>
|
||||
<value><string>$param_uri</string></value>
|
||||
</param>
|
||||
</params>
|
||||
</methodCall>
|
||||
EOS_XML
|
||||
return $self->{register_content};
|
||||
}
|
||||
$self->{rpc_server_params} //= $self->rpc_server_params;
|
||||
my $cfg = $self->{rpc_server_params};
|
||||
|
||||
sub unregister_content {
|
||||
my $self = shift;
|
||||
$c->log->debug("Snom prepare request for action $action");
|
||||
|
||||
my $param_macold = $self->content_params->{mac_old} // '';
|
||||
|
||||
$self->{unregister_content} = <<EOS_XML;
|
||||
<?xml version='1.0'?>
|
||||
<methodCall>
|
||||
<methodName>redirect.deregisterPhone</methodName>
|
||||
<params>
|
||||
<param>
|
||||
<value><string>$param_macold</string></value>
|
||||
</param>
|
||||
</params>
|
||||
</methodCall>
|
||||
EOS_XML
|
||||
return $self->{unregister_content};
|
||||
}
|
||||
around 'extract_response_description' => sub {
|
||||
my($orig_method, $self, $rpc_value) = @_;
|
||||
my $c = $self->params->{c};
|
||||
my $res = '';
|
||||
|
||||
if(ref $rpc_value eq 'ARRAY'){
|
||||
#1 - success; 0 - error, error string is a second param
|
||||
if($rpc_value->[0] eq '1'){
|
||||
$res = '';#clear the error
|
||||
}elsif($rpc_value->[0] eq '0'){
|
||||
return $rpc_value->[1];
|
||||
}else{
|
||||
$res = $self->unknown_error;
|
||||
}
|
||||
}else{
|
||||
$res = $self->unknown_error;
|
||||
# first, get company url
|
||||
my $url = "$$cfg{proto}://$$cfg{host}/$$cfg{path}/tokens/".$credentials->{id};
|
||||
$c->log->debug("Snom get tokens '$url'");
|
||||
my $req = HTTP::Request->new(GET => $url);
|
||||
$req->header('Authorization' => $self->generate_header($url, "GET", { credentials => $credentials, content_type => '', payload => '' }));
|
||||
$req->header('accept' => 'application/json');
|
||||
my $res = $self->_ua->request($req);
|
||||
my $data = decode_json($res->decoded_content);
|
||||
my $company_url = $data->{links}->{company};
|
||||
if ($res->is_success && $data->{links}->{company}) {
|
||||
$c->log->debug("Tokens fetching successful, data: " . $res->decoded_content);
|
||||
$url = $data->{links}->{company};
|
||||
} else {
|
||||
$c->log->error("Tokens fetching failed (" . $res->status_line . "): " . $res->decoded_content);
|
||||
return;
|
||||
}
|
||||
return $res;
|
||||
};
|
||||
|
||||
around 'process_bootstrap_uri' => sub {
|
||||
my($orig_method, $self, $uri) = @_;
|
||||
$uri = $self->$orig_method($uri);
|
||||
$uri = $self->bootstrap_uri_mac($uri);
|
||||
$self->content_params->{uri} = $uri;
|
||||
return $self->content_params->{uri};
|
||||
};
|
||||
|
||||
around 'bootstrap_uri_mac' => sub {
|
||||
my($orig_method, $self, $uri) = @_;
|
||||
if ($uri !~/\{mac\}$/){
|
||||
if ($uri !~/\/$/){
|
||||
$uri .= '/' ;
|
||||
|
||||
if ($action eq 'register_content') {
|
||||
# fetch product groups
|
||||
$url = "$$cfg{proto}://$$cfg{host}/$$cfg{path}/product-groups/";
|
||||
$c->log->debug("Snom fetch product groups '$url'");
|
||||
$req = HTTP::Request->new(GET => $url);
|
||||
$req->header('Authorization' => $self->generate_header($url, "GET", { credentials => $credentials, content_type => '', payload => '' }));
|
||||
$req->header('accept' => 'application/json');
|
||||
$res = $self->_ua->request($req);
|
||||
|
||||
my $product_group_id;
|
||||
my $setting_id;
|
||||
$data = decode_json($res->decoded_content);
|
||||
if ($res->is_success && scalar @$data) {
|
||||
$c->log->debug("Snom fetch product groups successful, data: " . $res->decoded_content);
|
||||
my ($product_group) = grep {$_->{name} eq $self->params->{redirect_params}->{product_family}} @$data;
|
||||
if ($product_group) {
|
||||
$product_group_id = $product_group->{uuid};
|
||||
}
|
||||
else {
|
||||
$c->log->error("Snom product group of specified product family not found.");
|
||||
return;
|
||||
}
|
||||
|
||||
#fetch settings
|
||||
$url = "$$cfg{proto}://$$cfg{host}/$$cfg{path}/settings/";
|
||||
$c->log->debug("Snom fetch settings '$url'");
|
||||
$req = HTTP::Request->new(GET => $url);
|
||||
$req->header('Authorization' => $self->generate_header($url, "GET", { credentials => $credentials, content_type => '', payload => '' }));
|
||||
$req->header('accept' => 'application/json');
|
||||
$res = $self->_ua->request($req);
|
||||
|
||||
$data = decode_json($res->decoded_content);
|
||||
if ($res->is_success && scalar @$data) {
|
||||
$c->log->debug("Snom fetch settings successful, data: " . $res->decoded_content);
|
||||
foreach my $setting (@$data) {
|
||||
if ($setting->{param_name} eq 'setting_server') {
|
||||
$setting_id = $setting->{uuid};
|
||||
}
|
||||
}
|
||||
unless ($setting_id) {
|
||||
$c->log->error("Snom setting for redirection server not found.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$c->log->error("Snom fetch product groups quey failed (" . $res->status_line . "): " . $res->decoded_content);
|
||||
return;
|
||||
}
|
||||
|
||||
# fetch profile
|
||||
$url = "$company_url/provisioning-profiles/";
|
||||
$c->log->debug("Snom check profiles '$url'");
|
||||
$req = HTTP::Request->new(GET => $url);
|
||||
$req->header('Authorization' => $self->generate_header($url, "GET", { credentials => $credentials, content_type => '', payload => '' }));
|
||||
$req->header('accept' => 'application/json');
|
||||
$res = $self->_ua->request($req);
|
||||
|
||||
$data = decode_json($res->decoded_content);
|
||||
if ($res->is_success && scalar @$data) {
|
||||
$c->log->debug("Snom check profiles query successful, data: " . $res->decoded_content);
|
||||
my $profile_id;
|
||||
my ($profile) = grep {$_->{name} eq $self->params->{redirect_params}->{profile}} @$data;
|
||||
if ($profile) {
|
||||
$profile_id = $profile->{uuid};
|
||||
}
|
||||
else {
|
||||
#profile does not exist, create it
|
||||
$c->log->debug("Snom create profile '$url'");
|
||||
$req = HTTP::Request->new(POST => $url);
|
||||
my $body = encode_json({
|
||||
name => $self->params->{redirect_params}->{profile},
|
||||
product_group => $product_group_id,
|
||||
autoprovisioning_enabled => 'true',
|
||||
});
|
||||
$req->header('Authorization' => $self->generate_header($url, "POST", { credentials => $credentials, content_type => '', payload => '' }));
|
||||
$req->header('accept' => 'application/json');
|
||||
$req->content_type('application/json');
|
||||
$req->content($body);
|
||||
$res = $self->_ua->request($req);
|
||||
$data = decode_json($res->decoded_content);
|
||||
if ($res->is_success && $res->code == 201) {
|
||||
$c->log->debug("Snom create profile query successful, data: " . $res->decoded_content);
|
||||
$profile_id = $data->{uuid};
|
||||
}
|
||||
else{
|
||||
$c->log->error("Snom create profile query failed (" . $res->status_line . "): " . $res->decoded_content);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
my $body = {
|
||||
mac => $new_mac,
|
||||
provisioning_profile => $profile_id,
|
||||
autoprovisioning_enabled => 'true',
|
||||
settings_manager => {
|
||||
$setting_id => {
|
||||
value => $param_uri,
|
||||
attrs => {
|
||||
perm => 'RW'
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
};
|
||||
$url = "$company_url/endpoints/$new_mac";
|
||||
$ret = {
|
||||
method =>'PUT',
|
||||
url => $url,
|
||||
body => $body,
|
||||
hawk => $self->generate_header($url, "PUT", { credentials => $credentials, content_type => '', payload => '' }),
|
||||
};
|
||||
} else {
|
||||
$c->log->error("Snom 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 = "$company_url/endpoints/";
|
||||
$c->log->debug("Snom check devices '$url'");
|
||||
$req = HTTP::Request->new(GET => $url);
|
||||
$req->header('Authorization' => $self->generate_header($url, "GET", { credentials => $credentials, content_type => '', payload => '' }));
|
||||
$req->header('accept' => 'application/json');
|
||||
$res = $self->_ua->request($req);
|
||||
$data = decode_json($res->decoded_content);
|
||||
if ($res->is_success && scalar @$data) {
|
||||
$c->log->debug("Snom check devices query successful, data: " . $res->decoded_content);
|
||||
my $device_id;
|
||||
my ($device) = grep {uc($_->{mac}) eq uc($old_mac)} @$data;
|
||||
if ($device) {
|
||||
$device_id = $device->{mac};
|
||||
}
|
||||
$c->log->debug("Snom unregister query successful, data: " . $res->decoded_content);
|
||||
$data = decode_json($res->decoded_content);
|
||||
$url = "$company_url/endpoints/$device_id";
|
||||
$ret = {
|
||||
method =>'DELETE',
|
||||
url => $url,
|
||||
body => undef,
|
||||
hawk => $self->generate_header($url, "DELETE", { credentials => $credentials, content_type => '', payload => '' }),
|
||||
};
|
||||
} else {
|
||||
$c->log->error("Snom unregister query failed (" . $res->status_line . "): " . $res->decoded_content);
|
||||
return;
|
||||
}
|
||||
$uri .= '?mac={mac}' ;
|
||||
}
|
||||
return $uri;
|
||||
};
|
||||
1;
|
||||
|
||||
=head1 NAME
|
||||
return $ret;
|
||||
}
|
||||
|
||||
sub generate_header {
|
||||
my ($self, $uri, $method, $options) = @_;
|
||||
|
||||
my $time = time;
|
||||
my $credentials = $options->{credentials};
|
||||
|
||||
my @chars = ("A".."Z", "a".."z");
|
||||
my $nonce;
|
||||
$nonce .= $chars[rand @chars] for 1..8;
|
||||
|
||||
$uri = URI->new($uri);
|
||||
|
||||
my $hash = $self->calculate_payload_hash($options->{payload}, $options->{content_type}, $credentials->{key});
|
||||
|
||||
my $artifacts = {
|
||||
ts => $time,
|
||||
nonce => $nonce,
|
||||
method => $method,
|
||||
resource => $uri->path_query,
|
||||
host => $uri->host,
|
||||
port => $uri->port,
|
||||
hash => $hash || ''
|
||||
};
|
||||
|
||||
my $mac = $self->calculate_mac($credentials, $artifacts);
|
||||
|
||||
my $auth = 'Hawk';
|
||||
$auth .= ' mac="' . $mac . '",';
|
||||
$auth .= ' hash="' . $artifacts->{hash} . '",' unless $hash eq '';
|
||||
$auth .= ' id="' . $credentials->{id} . '",';
|
||||
$auth .= ' ts="' . $artifacts->{ts} . '",';
|
||||
$auth .= ' nonce="' . $artifacts->{nonce} .'"';
|
||||
|
||||
return $auth;
|
||||
|
||||
NGCP::Panel::Utils::DeviceBootstrap
|
||||
}
|
||||
|
||||
sub calculate_mac {
|
||||
my ($self, $credentials, $options) = @_;
|
||||
|
||||
=head1 DESCRIPTION
|
||||
my $normalized = $self->generate_normalized_string($options);
|
||||
|
||||
Make API requests to configure remote redirect servers for requested MAC with autorpov uri.
|
||||
See http://wiki.snom.com/Category:HowTo:XMLRPC_Redirection.
|
||||
my $result_b64 = "";
|
||||
$result_b64 = hmac_sha256_base64($normalized, $credentials->{key});
|
||||
while (length($result_b64) % 4) {
|
||||
$result_b64 .= '=';
|
||||
}
|
||||
|
||||
=head1 METHODS
|
||||
return $result_b64;
|
||||
}
|
||||
|
||||
=head2 bootstrap
|
||||
sub calculate_payload_hash {
|
||||
my ($self, $payload, $content_type, $key) = @_;
|
||||
|
||||
Dispatch to proper vendor API call.
|
||||
return '' if $payload eq '';
|
||||
|
||||
=head1 AUTHOR
|
||||
my $pload = "hawk.1.payload\n";
|
||||
$pload .= $content_type . "\n";
|
||||
$pload .= ($payload || '');
|
||||
|
||||
Irina Peshinskaya C<< <ipeshinskaya@sipwise.com> >>
|
||||
my $result_b64 = hmac_sha256_base64($pload, $key);
|
||||
|
||||
=head1 LICENSE
|
||||
while (length($result_b64) % 4) {
|
||||
$result_b64 .= '=';
|
||||
}
|
||||
|
||||
This library is free software. You can redistribute it and/or modify
|
||||
it under the same terms as Perl itself.
|
||||
return $result_b64;
|
||||
}
|
||||
|
||||
=cut
|
||||
# vim: set tabstop=4 expandtab:
|
||||
sub generate_normalized_string {
|
||||
my ($self, $options) = @_;
|
||||
|
||||
my $normalized = "hawk.1.header\n";
|
||||
$normalized .= $options->{ts}."\n";
|
||||
$normalized .= $options->{nonce}."\n";
|
||||
$normalized .= uc($options->{method}) . "\n";
|
||||
$normalized .= $options->{resource}."\n";
|
||||
$normalized .= $options->{host}."\n";
|
||||
$normalized .= $options->{port}."\n";
|
||||
$normalized .= "\n";
|
||||
$normalized .= "\n"; # this is also needed for a healthy header ( and mac ) since an extension is allowed in hawk
|
||||
|
||||
return $normalized;
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
Loading…
Reference in new issue