You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
ngcp-panel/lib/NGCP/Panel/Controller/Device.pm

2122 lines
78 KiB

package NGCP::Panel::Controller::Device;
use NGCP::Panel::Utils::Generic qw(:all);
use Sipwise::Base;
use Template;
use Crypt::Rijndael;
use Digest::MD5 qw/md5_hex/;
use Storable qw/freeze/;
use JSON qw(decode_json encode_json);
use NGCP::Panel::Form;
use NGCP::Panel::Utils::Navigation;
use NGCP::Panel::Utils::DeviceBootstrap;
use NGCP::Panel::Utils::Device;
use NGCP::Panel::Utils::DeviceFirmware;
use NGCP::Panel::Utils::DateTime;
use NGCP::Panel::Utils::Preferences;
use DateTime::Format::HTTP;
use parent 'Catalyst::Controller';
sub auto :Private {
my ($self, $c) = @_;
$c->log->debug(__PACKAGE__ . '::auto');
return 1;
}
sub base :Chained('/') :PathPart('device') :CaptureArgs(0) {
my ($self, $c) = @_;
NGCP::Panel::Utils::Navigation::check_redirect_chain(c => $c);
# TODO: move out fw/profile/config fetching to separate func to not
# load it for subscriber access?
my $reseller_id;
if($c->user->roles eq 'reseller') {
$reseller_id = $c->user->reseller_id;
} elsif($c->user->roles eq 'subscriber' || $c->user->roles eq 'subscriberadmin') {
$reseller_id = $c->user->voip_subscriber->contract->contact->reseller_id;
}
my $devmod_rs = $c->model('DB')->resultset('autoprov_devices')->search_rs(undef,{
'columns' => [qw/id reseller_id type vendor model front_image_type mac_image_type front_thumbnail_type num_lines bootstrap_method bootstrap_uri extensions_num/,
{
mac_image_exists => \'mac_image is not null',
front_image_exists => \'front_image is not null',
front_thumbnail_exists => \'front_thumbnail is not null',
}
],
});
$reseller_id and $devmod_rs = $devmod_rs->search({ reseller_id => $reseller_id });
$c->stash->{devmod_dt_columns} = NGCP::Panel::Utils::Datatables::set_columns($c, [
{ name => 'id', search => 1, title => $c->loc('#') },
{ name => 'type', search => 1, title => $c->loc('Type') },
{ name => 'reseller.name', search => 1, title => $c->loc('Reseller') },
{ name => 'vendor', search => 1, title => $c->loc('Vendor') },
{ name => 'model', search => 1, title => $c->loc('Model') },
]);
my $devfw_rs = $c->model('DB')->resultset('autoprov_firmwares')->search_rs(undef,{'columns' => [qw/id device_id version filename tag/],
});
$reseller_id and $devfw_rs = $devfw_rs->search({
'device.reseller_id' => $reseller_id,
},{
join => 'device',
});
$c->stash->{devfw_dt_columns} = NGCP::Panel::Utils::Datatables::set_columns($c, [
{ name => 'id', search => 1, title => $c->loc('#') },
{ name => 'device.reseller.name', search => 1, title => $c->loc('Reseller') },
{ name => 'device.vendor', search => 1, title => $c->loc('Device Vendor') },
{ name => 'device.model', search => 1, title => $c->loc('Device Model') },
{ name => 'filename', search => 1, title => $c->loc('Firmware File') },
{ name => 'version', search => 1, title => $c->loc('Version') },
{ name => 'tag', search => 1, title => $c->loc('Firmware Tag') },
]);
my $devconf_rs = $c->model('DB')->resultset('autoprov_configs')->search_rs(undef,{'columns' => [qw/id device_id version content_type/],
});
$reseller_id and $devconf_rs = $devconf_rs->search({
'device.reseller_id' => $reseller_id,
}, {
join => 'device',
});
$c->stash->{devconf_dt_columns} = NGCP::Panel::Utils::Datatables::set_columns($c, [
{ name => 'id', search => 1, title => $c->loc('#') },
{ name => 'device.reseller.name', search => 1, title => $c->loc('Reseller') },
{ name => 'device.vendor', search => 1, title => $c->loc('Device Vendor') },
{ name => 'device.model', search => 1, title => $c->loc('Device Model') },
{ name => 'version', search => 1, title => $c->loc('Version') },
]);
my $devprof_rs = $c->model('DB')->resultset('autoprov_profiles');
$reseller_id and $devprof_rs = $devprof_rs->search({
'device.reseller_id' => $reseller_id,
}, {
join => { 'config' => 'device' },
});
$c->stash->{devprof_dt_columns} = NGCP::Panel::Utils::Datatables::set_columns($c, [
{ name => 'id', search => 1, title => $c->loc('#') },
{ name => 'config.device.reseller.name', search => 1, title => $c->loc('Reseller') },
{ name => 'name', search => 1, title => $c->loc('Name') },
{ name => 'config.device.vendor', search => 1, title => $c->loc('Device Vendor') },
{ name => 'config.device.model', search => 1, title => $c->loc('Device Model') },
# { name => 'firmware.filename', search => 1, title => $c->loc('Firmware File') },
{ name => 'config.version', search => 1, title => $c->loc('Configuration Version') },
]);
my $fielddev_rs = $c->model('DB')->resultset('autoprov_field_devices');
$reseller_id and $fielddev_rs = $fielddev_rs->search({
'device.reseller_id' => $reseller_id,
},{
join => { 'profile' => { 'config' => 'device' } },
});
$c->stash->{fielddev_dt_columns} = NGCP::Panel::Utils::Datatables::set_columns($c, [
{ name => 'id', search => 1, title => $c->loc('#') },
{ name => 'identifier', search => 1, title => $c->loc('MAC Address / Identifier') },
{ name => 'profile.name', search => 1, title => $c->loc('Profile Name') },
{ name => 'contract.id', search => 1, title => $c->loc('Customer #') },
{ name => 'contract.contact.email', search => 1, title => $c->loc('Customer Email') },
]);
my $extensions_rs = $c->model('DB')->resultset('autoprov_devices')->search_rs({
'type' => 'extension',
});
$reseller_id and $extensions_rs = $extensions_rs->search({ reseller_id => $reseller_id });
$c->stash->{fielddev_dt_columns} = NGCP::Panel::Utils::Datatables::set_columns($c, [
{ name => 'id', search => 1, title => $c->loc('#') },
{ name => 'identifier', search => 1, title => $c->loc('MAC Address / Identifier') },
{ name => 'profile.name', search => 1, title => $c->loc('Profile Name') },
{ name => 'contract.id', search => 1, title => $c->loc('Customer #') },
{ name => 'contract.contact.email', search => 1, title => $c->loc('Customer Email') },
]);
$c->stash(
devmod_rs => $devmod_rs,
devfw_rs => $devfw_rs,
devconf_rs => $devconf_rs,
devprof_rs => $devprof_rs,
fielddev_rs => $fielddev_rs,
extensions_rs => $extensions_rs,
reseller_id => $reseller_id,
template => 'device/list.tt',
);
}
sub root :Chained('base') :PathPart('') :Args(0) :Does(License) :RequiresLicense('pbx') :RequiresLicense('device_provisioning') :LicenseDetachTo('/denied_page') :Does(ACL) :ACLDetachTo('/denied_page') :AllowedRole(admin) :AllowedRole(reseller) {
my ($self, $c) = @_;
}
sub devmod_ajax :Chained('base') :PathPart('model/ajax') :Args(0) :Does(ACL) :ACLDetachTo('/denied_page') :AllowedRole(admin) :AllowedRole(reseller) {
my ($self, $c) = @_;
my $resultset = $c->stash->{devmod_rs};
NGCP::Panel::Utils::Datatables::process($c, $resultset, $c->stash->{devmod_dt_columns},
sub {
my ($result) = @_;
my %data = (
mac_image_exists => $result->get_column('mac_image_exists'),
front_image_exists => $result->get_column('front_image_exists'),
front_thumbnail_exists => $result->get_column('front_thumbnail_exists'),
);
return %data
},
);
$c->detach( $c->view("JSON") );
}
sub extensionmodel_ajax :Chained('base') :PathPart('extensionmodel/ajax') :Args(0) :Does(ACL) :ACLDetachTo('/denied_page') :AllowedRole(admin) :AllowedRole(reseller) {
my ($self, $c) = @_;
my $resultset = $c->stash->{devmod_rs}->search_rs({
'me.type' => 'extension',
});
NGCP::Panel::Utils::Datatables::process($c, $resultset, $c->stash->{devmod_dt_columns},
sub {
my ($result) = @_;
my %data = (
mac_image_exists => $result->get_column('mac_image_exists'),
front_image_exists => $result->get_column('front_image_exists'),
front_thumbnail_exists => $result->get_column('front_thumbnail_exists'),
);
return %data
},
);
$c->detach( $c->view("JSON") );
}
sub devmod_create :Chained('base') :PathPart('model/create') :Args(0) :Does(ACL) :ACLDetachTo('/denied_page') :AllowedRole(admin) :AllowedRole(reseller) {
my ($self, $c) = @_;
my $posted = ($c->request->method eq 'POST');
my $form;
if($c->user->is_superuser) {
$form = NGCP::Panel::Form::get("NGCP::Panel::Form::Device::ModelAdmin", $c);
} else {
$form = NGCP::Panel::Form::get("NGCP::Panel::Form::Device::Model", $c);
}
my $params = {};
$params = merge($params, $c->session->{created_objects});
if($posted) {
$c->req->params->{front_image} = $c->req->upload('front_image');
$c->req->params->{mac_image} = $c->req->upload('mac_image');
$c->req->params->{front_thumbnail} = $c->req->upload('front_thumbnail');
}
$form->process(
posted => $posted,
params => $c->request->params,
item => $params
);
NGCP::Panel::Utils::Navigation::check_form_buttons(
c => $c,
form => $form,
fields => {
'reseller.create' => $c->uri_for('/reseller/create'),
},
back_uri => $c->req->uri,
);
if($posted && $form->validated) {
try {
my $schema = $c->model('DB');
$schema->txn_do(sub {
if($c->user->is_superuser) {
$form->values->{reseller_id} = $form->values->{reseller}{id};
} else {
$form->values->{reseller_id} = $c->user->reseller_id;
}
delete $form->values->{reseller};
my $ft = File::Type->new();
foreach(qw/front_image mac_image front_thumbnail/){
if($form->values->{$_}) {
my $image = delete $form->values->{$_};
$form->values->{$_} = $image->slurp;
$form->values->{$_.'_type'} = $ft->mime_type($form->values->{$_});
}
}
#preparation of the connectable_models before store_and_process_device_model call
if(defined $form->values->{connectable_models}){
$form->values->{connectable_models} = decode_json($form->values->{connectable_models});
}
my $devmod = NGCP::Panel::Utils::Device::store_and_process_device_model($c, undef, $form->values);
delete $c->session->{created_objects}->{reseller};
$c->session->{created_objects}->{device} = { id => $devmod->id };
});
NGCP::Panel::Utils::Message::info(
c => $c,
desc => $c->loc('Successfully created device model'),
);
} catch($e) {
NGCP::Panel::Utils::Message::error(
c => $c,
error => $e,
desc => $c->loc('Failed to create device model'),
);
}
NGCP::Panel::Utils::Navigation::back_or($c, $c->uri_for('/device'));
}
$c->stash(
devmod_create_flag => 1,
form => $form,
);
}
sub prepare_connectable :Private{
my ($self, $c, $model) = @_;
my $values = [];
my $connected_rs = $c->model('DB')->resultset('autoprov_device_extensions')->search_rs({
( $model->type eq 'phone' ? 'device_id' : 'extension_id' ) => $model->id,
});
for my $connected($connected_rs->all) {
push @$values, $connected->get_column ( $model->type eq 'phone' ? 'extension_id' : 'device_id' ) ;
}
return (encode_json($values), $values);
}
sub devmod_base :Chained('base') :PathPart('model') :CaptureArgs(1) {
my ($self, $c, $devmod_id) = @_;
unless(is_int($devmod_id)) {
NGCP::Panel::Utils::Message::error(
c => $c,
error => "invalid device model id '$devmod_id'",
desc => $c->loc('Invalid device model id'),
);
NGCP::Panel::Utils::Navigation::back_or($c, $c->uri_for('/device'));
}
my $devmod = $c->stash->{devmod_rs}->find($devmod_id,{'+columns' => [qw/mac_image front_image front_thumbnail/]});
unless($devmod) {
NGCP::Panel::Utils::Message::error(
c => $c,
error => "device model with id '$devmod_id' not found",
desc => $c->loc('Device model not found'),
);
NGCP::Panel::Utils::Navigation::back_or($c, $c->uri_for('/device'));
}
$c->stash(
devmod => $devmod,
);
}
sub devmod_delete :Chained('devmod_base') :PathPart('delete') :Args(0) :Does(ACL) :ACLDetachTo('/denied_page') :AllowedRole(admin) :AllowedRole(reseller) {
my ($self, $c) = @_;
try {
$c->stash->{devmod}->delete;
NGCP::Panel::Utils::Message::info(
c => $c,
data => { id => $c->stash->{devmod}->id,
model => $c->stash->{devmod}->model,
vendor => $c->stash->{devmod}->vendor },
desc => $c->loc('Device model successfully deleted'),
);
} catch($e) {
NGCP::Panel::Utils::Message::error(
c => $c,
error => "failed to delete device model with id '".$c->stash->{devmod}->id."': $e",
desc => $c->loc('Failed to delete device model'),
);
}
NGCP::Panel::Utils::Navigation::back_or($c, $c->uri_for('/device'));
}
sub devmod_edit :Chained('devmod_base') :PathPart('edit') :Args(0) :Does(ACL) :ACLDetachTo('/denied_page') :AllowedRole(admin) :AllowedRole(reseller) {
my ($self, $c) = @_;
my $posted = ($c->request->method eq 'POST');
my $form;
my $params = { $c->stash->{devmod}->get_inflated_columns };
$params->{linerange} = [];
foreach my $range($c->stash->{devmod}->autoprov_device_line_ranges->all) {
my $keys = [];
foreach my $key($range->annotations->all) {
push @{ $keys }, { x => $key->x, y => $key->y, labelpos => $key->position };
}
my $r = { $range->get_inflated_columns };
$r->{keys} = $keys;
push @{ $params->{linerange} }, $r;
}
#TODO: TO inflate/deflate, I think
foreach ( $c->model('DB')->resultset('autoprov_sync')->search_rs({
device_id =>$c->stash->{devmod}->id,
})->all ){
$params->{'bootstrap_config_'.$c->stash->{devmod}->bootstrap_method.'_'.$_->autoprov_sync_parameters->parameter_name} = $_->parameter_value;
}
my $credentials_rs = $c->model('DB')->resultset('autoprov_redirect_credentials')->search_rs({
'me.device_id' => $c->stash->{devmod}->id,
});
if($credentials_rs->first){
foreach ( qw/user password/ ){
$params->{'bootstrap_config_'.$c->stash->{devmod}->bootstrap_method.'_'.$_} = $credentials_rs->first->get_column($_);
}
}
#edit specific
($params->{connectable_models}) = $self->prepare_connectable($c, $c->stash->{devmod});
$params->{reseller}{id} = delete $params->{reseller_id};
$params = merge($params, $c->session->{created_objects});
$c->stash(edit_model => 1); # to make front_image optional
if($c->user->is_superuser) {
$form = NGCP::Panel::Form::get("NGCP::Panel::Form::Device::ModelAdmin", $c);
} else {
$form = NGCP::Panel::Form::get("NGCP::Panel::Form::Device::Model", $c);
}
if($posted) {
$c->req->params->{front_image} = $c->req->upload('front_image');
$c->req->params->{mac_image} = $c->req->upload('mac_image');
$c->req->params->{front_thumbnail} = $c->req->upload('front_thumbnail');
}
$form->process(
posted => $posted,
params => $c->request->params,
item => $params
);
NGCP::Panel::Utils::Navigation::check_form_buttons(
c => $c,
form => $form,
fields => {
'reseller.create' => $c->uri_for('/reseller/create'),
},
back_uri => $c->req->uri,
);
if($posted && $form->validated) {
try {
my $schema = $c->model('DB');
$schema->txn_do(sub {
if($c->user->is_superuser) {
$form->values->{reseller_id} = $form->values->{reseller}{id};
} else {
$form->values->{reseller_id} = $c->user->reseller_id;
}
delete $form->values->{reseller};
foreach (qw/front_image mac_image front_thumbnail/){
if($form->values->{$_}) {
my $image = delete $form->values->{$_};
$form->values->{$_} = $image->slurp;
$form->values->{$_.'_type'} = $image->type;
} else {
delete $form->values->{$_};
delete $form->values->{$_.'_type'};
}
}
#preparation of the connectable_models before store_and_process_device_model call
if(defined $form->values->{connectable_models}){
$form->values->{connectable_models} = decode_json($form->values->{connectable_models});
}
NGCP::Panel::Utils::Device::store_and_process_device_model($c, $c->stash->{devmod}, $form->values);
delete $c->session->{created_objects}->{reseller};
});
NGCP::Panel::Utils::Message::info(
c => $c,
data => { id => $c->stash->{devmod}->id,
model => $c->stash->{devmod}->model,
vendor => $c->stash->{devmod}->vendor },
desc => $c->loc('Successfully updated device model'),
);
} catch($e) {
NGCP::Panel::Utils::Message::error(
c => $c,
error => $e,
desc => $c->loc('Failed to update device model'),
);
}
NGCP::Panel::Utils::Navigation::back_or($c, $c->uri_for('/device'));
}
$c->stash(
devmod_edit_flag => 1,
form => $form,
);
}
sub devmod_download_frontimage_by_profile :Chained('devprof_base') :PathPart('frontimage') :Args(0) {
my ($self, $c) = @_;
my $devprof = $c->stash->{devprof};
my $devmod = $devprof->config->device;
unless($devmod->front_image) {
$c->response->body($c->loc('404 - No front image available for the model of this device profile'));
$c->response->status(404);
return;
}
$c->response->content_type($devmod->front_image_type);
$c->response->body($devmod->front_image);
}
sub devmod_download_frontimage :Chained('devmod_base') :PathPart('frontimage') :Args(0) {
my ($self, $c) = @_;
my $devmod = $c->stash->{devmod};
unless($devmod->front_image) {
$c->response->body($c->loc('404 - No front image available for this device model'));
$c->response->status(404);
return;
}
$c->response->content_type($devmod->front_image_type);
$c->response->body($devmod->front_image);
}
sub devmod_download_macimage :Chained('devmod_base') :PathPart('macimage') :Args(0) {
my ($self, $c) = @_;
my $devmod = $c->stash->{devmod};
unless($devmod->mac_image) {
$c->response->body($c->loc('404 - No mac image available for this device model'));
$c->response->status(404);
return;
}
$c->response->content_type($devmod->mac_image_type);
$c->response->body($devmod->mac_image);
}
sub devmod_download_frontthumbnail_by_profile :Chained('devprof_base') :PathPart('frontthumbnail') :Args(0) {
my ($self, $c) = @_;
my $devprof = $c->stash->{devprof};
my $devmod = $devprof->config->device;
unless($devmod->front_thumbnail) {
$c->log->info("No device thumbnail for profile id " . $c->stash->{devprof}->id . ", redirecting to front image");
$c->res->redirect($c->uri_for_action("/device/devmod_download_frontimage_by_profile", [$c->stash->{devprof}->id]));
return;
}
$c->response->content_type($devmod->front_thumbnail_type);
$c->response->body($devmod->front_thumbnail);
}
sub devmod_download_frontthumbnail :Chained('devmod_base') :PathPart('frontthumbnail') :Args(0) {
my ($self, $c) = @_;
my $devmod = $c->stash->{devmod};
unless($devmod->front_thumbnail) {
$c->log->info("No device thumbnail for model id " . $c->stash->{devmod}->id . ", redirecting to front image");
$c->res->redirect($c->uri_for_action("/device/devmod_download_frontimage", [$c->stash->{devmod}->id]));
return;
}
$c->response->content_type($devmod->front_thumbnail_type);
$c->response->body($devmod->front_thumbnail);
}
sub devfw_ajax :Chained('base') :PathPart('firmware/ajax') :Args(0) :Does(ACL) :ACLDetachTo('/denied_page') :AllowedRole(admin) :AllowedRole(reseller) {
my ($self, $c) = @_;
my $resultset = $c->stash->{devfw_rs};
NGCP::Panel::Utils::Datatables::process($c, $resultset, $c->stash->{devfw_dt_columns});
$c->detach( $c->view("JSON") );
}
sub devfw_create :Chained('base') :PathPart('firmware/create') :Args(0) :Does(ACL) :ACLDetachTo('/denied_page') :AllowedRole(admin) :AllowedRole(reseller) {
my ($self, $c) = @_;
my $posted = ($c->request->method eq 'POST');
my $form = NGCP::Panel::Form::get("NGCP::Panel::Form::Device::Firmware", $c);
my $params = {};
$params = merge($params, $c->session->{created_objects});
if($posted) {
$c->req->params->{data} = $c->req->upload('data');
}
$form->process(
posted => $posted,
params => $c->request->params,
item => $params
);
NGCP::Panel::Utils::Navigation::check_form_buttons(
c => $c,
form => $form,
fields => {
'device.create' => $c->uri_for('/device/model/create'),
},
back_uri => $c->req->uri,
);
if($posted && $form->validated) {
try {
my $schema = $c->model('DB');
$schema->txn_do(sub {
my $file = delete $form->values->{data};
$form->values->{filename} = $file->filename;
my $devmod = $c->stash->{devmod_rs}->find($form->values->{device}{id},{'+columns' => [qw/mac_image front_image front_thumbnail/]});
my $devfw = $devmod->create_related('autoprov_firmwares', $form->values);
if ($file->size) {
NGCP::Panel::Utils::DeviceFirmware::insert_firmware_data(
c => $c, fw_id => $devfw->id, data_fh => $file->fh
);
}
delete $c->session->{created_objects}->{device};
$c->session->{created_objects}->{firmware} = { id => $devfw->id };
});
NGCP::Panel::Utils::Message::info(
c => $c,
desc => $c->loc('Successfully created device firmware'),
);
} catch($e) {
NGCP::Panel::Utils::Message::error(
c => $c,
error => $e,
desc => $c->loc('Failed to create device firmware'),
);
}
NGCP::Panel::Utils::Navigation::back_or($c, $c->uri_for('/device'));
}
$c->stash(
devfw_create_flag => 1,
form => $form,
);
}
sub devfw_base :Chained('base') :PathPart('firmware') :CaptureArgs(1) :Does(ACL) :ACLDetachTo('/denied_page') :AllowedRole(admin) :AllowedRole(reseller) {
my ($self, $c, $devfw_id) = @_;
unless(is_int($devfw_id)) {
NGCP::Panel::Utils::Message::error(
c => $c,
error => "invalid device firmware id '$devfw_id'",
desc => $c->loc('Invalid device firmware id'),
);
NGCP::Panel::Utils::Navigation::back_or($c, $c->uri_for('/device'));
}
$c->stash->{devfw} = $c->stash->{devfw_rs}->find($devfw_id);
unless($c->stash->{devfw}) {
NGCP::Panel::Utils::Message::error(
c => $c,
error => "device firmware with id '$devfw_id' not found",
desc => $c->loc('Device firmware not found'),
);
NGCP::Panel::Utils::Navigation::back_or($c, $c->uri_for('/device'));
}
}
sub devfw_delete :Chained('devfw_base') :PathPart('delete') :Args(0) {
my ($self, $c) = @_;
try {
$c->stash->{devfw}->delete;
NGCP::Panel::Utils::Message::info(
c => $c,
data => { $c->stash->{devfw}->get_inflated_columns },
desc => $c->loc('Device firmware successfully deleted'),
);
} catch($e) {
NGCP::Panel::Utils::Message::error(
c => $c,
error => "failed to delete device firmware with id '".$c->stash->{devfw}->id."': $e",
desc => $c->loc('Failed to delete device firmware'),
);
}
NGCP::Panel::Utils::Navigation::back_or($c, $c->uri_for('/device'));
}
sub devfw_edit :Chained('devfw_base') :PathPart('edit') :Args(0) {
my ($self, $c) = @_;
my $posted = ($c->request->method eq 'POST');
my $form;
my $params = { $c->stash->{devfw}->get_inflated_columns };
$params->{device}{id} = delete $params->{device_id};
$params = merge($params, $c->session->{created_objects});
if($posted) {
$c->req->params->{data} = $c->req->upload('data');
}
$form = NGCP::Panel::Form::get("NGCP::Panel::Form::Device::Firmware", $c);
$form->process(
posted => $posted,
params => $c->request->params,
item => $params
);
NGCP::Panel::Utils::Navigation::check_form_buttons(
c => $c,
form => $form,
fields => {
'device.create' => $c->uri_for('/device/model/create'),
},
back_uri => $c->req->uri,
);
if($posted && $form->validated) {
try {
my $schema = $c->model('DB');
$schema->txn_do(sub {
$form->values->{device_id} = $form->values->{device}{id};
delete $form->values->{device};
my $file = delete $form->values->{data};
$form->values->{filename} = $file->filename;
$c->stash->{devfw}->update($form->values);
if ($file->size) {
NGCP::Panel::Utils::DeviceFirmware::insert_firmware_data(
c => $c,
fw_id => $c->stash->{devfw}->id,
data_fh => $file->fh
);
}
delete $c->session->{created_objects}->{device};
});
NGCP::Panel::Utils::Message::info(
c => $c,
desc => $c->loc('Successfully updated device firmware'),
);
} catch($e) {
NGCP::Panel::Utils::Message::error(
c => $c,
error => $e,
desc => $c->loc('Failed to update device firmware'),
);
}
NGCP::Panel::Utils::Navigation::back_or($c, $c->uri_for('/device'));
}
$c->stash(
devfw_edit_flag => 1,
form => $form,
);
}
sub devfw_download :Chained('devfw_base') :PathPart('download') :Args(0) {
my ($self, $c) = @_;
my $fw = $c->stash->{devfw};
$c->response->header ('Content-Disposition' => 'attachment; filename="' . $fw->filename . '"');
$c->response->content_type('application/octet-stream');
$c->response->body(
NGCP::Panel::Utils::DeviceFirmware::get_firmware_data(
c => $c,
fw_id => $fw->id
));
}
sub devconf_ajax :Chained('base') :PathPart('config/ajax') :Args(0) :Does(ACL) :ACLDetachTo('/denied_page') :AllowedRole(admin) :AllowedRole(reseller) {
my ($self, $c) = @_;
my $resultset = $c->stash->{devconf_rs};
NGCP::Panel::Utils::Datatables::process($c, $resultset, $c->stash->{devconf_dt_columns});
$c->detach( $c->view("JSON") );
}
sub devconf_create :Chained('base') :PathPart('config/create') :Args(0) :Does(ACL) :ACLDetachTo('/denied_page') :AllowedRole(admin) :AllowedRole(reseller) {
my ($self, $c) = @_;
my $posted = ($c->request->method eq 'POST');
my $form = NGCP::Panel::Form::get("NGCP::Panel::Form::Device::Config", $c);
my $params = {};
$params = merge($params, $c->session->{created_objects});
$form->process(
posted => $posted,
params => $c->request->params,
item => $params
);
NGCP::Panel::Utils::Navigation::check_form_buttons(
c => $c,
form => $form,
fields => {
'device.create' => $c->uri_for('/device/model/create'),
},
back_uri => $c->req->uri,
);
if($posted && $form->validated) {
try {
my $schema = $c->model('DB');
$schema->txn_do(sub {
my $devmod = $c->stash->{devmod_rs}->find($form->values->{device}{id},{'+columns' => [qw/mac_image front_image front_thumbnail/]});
my $devconf = $devmod->create_related('autoprov_configs', $form->values);
delete $c->session->{created_objects}->{device};
$c->session->{created_objects}->{config} = { id => $devconf->id };
});
NGCP::Panel::Utils::Message::info(
c => $c,
desc => $c->loc('Successfully created device configuration'),
);
} catch($e) {
NGCP::Panel::Utils::Message::error(
c => $c,
error => $e,
desc => $c->loc('Failed to create device configuration'),
);
}
NGCP::Panel::Utils::Navigation::back_or($c, $c->uri_for('/device'));
}
$c->stash(
devconf_create_flag => 1,
form => $form,
);
}
sub devconf_base :Chained('base') :PathPart('config') :CaptureArgs(1) :Does(ACL) :ACLDetachTo('/denied_page') :AllowedRole(admin) :AllowedRole(reseller) {
my ($self, $c, $devconf_id) = @_;
unless(is_int($devconf_id)) {
NGCP::Panel::Utils::Message::error(
c => $c,
error => "invalid device config id '$devconf_id'",
desc => $c->loc('Invalid device configuration id'),
);
NGCP::Panel::Utils::Navigation::back_or($c, $c->uri_for('/device'));
}
$c->stash->{devconf} = $c->stash->{devconf_rs}->find($devconf_id,{'+columns' => 'data'});
unless($c->stash->{devconf}) {
NGCP::Panel::Utils::Message::error(
c => $c,
error => "device configuration with id '$devconf_id' not found",
desc => $c->loc('Device configuration not found'),
);
NGCP::Panel::Utils::Navigation::back_or($c, $c->uri_for('/device'));
}
}
sub devconf_delete :Chained('devconf_base') :PathPart('delete') :Args(0) {
my ($self, $c) = @_;
try {
$c->stash->{devconf}->delete;
NGCP::Panel::Utils::Message::info(
c => $c,
data => { $c->stash->{devconf}->get_inflated_columns },
desc => $c->loc('Device configuration successfully deleted'),
);
} catch($e) {
NGCP::Panel::Utils::Message::error(
c => $c,
error => "failed to delete device configuration with id '".$c->stash->{devconf}->id."': $e",
desc => $c->loc('Failed to delete device configuration'),
);
}
NGCP::Panel::Utils::Navigation::back_or($c, $c->uri_for('/device'));
}
sub devconf_edit :Chained('devconf_base') :PathPart('edit') :Args(0) {
my ($self, $c) = @_;
my $posted = ($c->request->method eq 'POST');
my $form;
my $params = { $c->stash->{devconf}->get_inflated_columns };
$params->{device}{id} = delete $params->{device_id};
$params = merge($params, $c->session->{created_objects});
$form = NGCP::Panel::Form::get("NGCP::Panel::Form::Device::Config", $c);
$form->process(
posted => $posted,
params => $c->request->params,
item => $params
);
NGCP::Panel::Utils::Navigation::check_form_buttons(
c => $c,
form => $form,
fields => {
'device.create' => $c->uri_for('/device/model/create'),
},
back_uri => $c->req->uri,
);
if($posted && $form->validated) {
try {
my $schema = $c->model('DB');
$schema->txn_do(sub {
$form->values->{device_id} = $form->values->{device}{id};
delete $form->values->{device};
use Data::Printer; p $form->values;
$c->stash->{devconf}->update($form->values);
delete $c->session->{created_objects}->{device};
});
NGCP::Panel::Utils::Message::info(
c => $c,
desc => $c->loc('Successfully updated device configuration'),
);
} catch($e) {
NGCP::Panel::Utils::Message::error(
c => $c,
error => $e,
desc => $c->loc('Failed to update device configuration'),
);
}
NGCP::Panel::Utils::Navigation::back_or($c, $c->uri_for('/device'));
}
$c->stash(
devconf_edit_flag => 1,
form => $form,
);
}
sub devconf_download :Chained('devconf_base') :PathPart('download') :Args(0) {
my ($self, $c) = @_;
my $conf = $c->stash->{devconf};
$c->response->content_type($conf->content_type);
$c->response->body($conf->data);
}
sub devprof_ajax :Chained('base') :PathPart('profile/ajax') :Args(0) :Does(ACL) :ACLDetachTo('/denied_page') :AllowedRole(admin) :AllowedRole(reseller) {
my ($self, $c) = @_;
my $resultset = $c->stash->{devprof_rs};
NGCP::Panel::Utils::Datatables::process($c, $resultset, $c->stash->{devprof_dt_columns});
$c->detach( $c->view("JSON") );
}
sub devprof_create :Chained('base') :PathPart('profile/create') :Args(0) :Does(ACL) :ACLDetachTo('/denied_page') :AllowedRole(admin) :AllowedRole(reseller) {
my ($self, $c) = @_;
my $posted = ($c->request->method eq 'POST');
my $form = NGCP::Panel::Form::get("NGCP::Panel::Form::Device::Profile", $c);
my $params = {};
$params = merge($params, $c->session->{created_objects});
$form->process(
posted => $posted,
params => $c->request->params,
item => $params
);
NGCP::Panel::Utils::Navigation::check_form_buttons(
c => $c,
form => $form,
fields => {
'config.create' => $c->uri_for('/device/config/create'),
},
back_uri => $c->req->uri,
);
if($posted && $form->validated) {
try {
my $schema = $c->model('DB');
$schema->txn_do(sub {
$form->values->{config_id} = $form->values->{config}{id};
delete $form->values->{config};
$c->model('DB')->resultset('autoprov_profiles')->create($form->values);
delete $c->session->{created_objects}->{config};
});
NGCP::Panel::Utils::Message::info(
c => $c,
desc => $c->loc('Successfully created device profile'),
);
} catch($e) {
NGCP::Panel::Utils::Message::error(
c => $c,
error => $e,
desc => $c->loc('Failed to create device profile'),
);
}
NGCP::Panel::Utils::Navigation::back_or($c, $c->uri_for('/device'));
}
$c->stash(
devprof_create_flag => 1,
form => $form,
);
}
sub devprof_base :Chained('base') :PathPart('profile') :CaptureArgs(1) :Does(ACL) :ACLDetachTo('/denied_page') :AllowedRole(admin) :AllowedRole(reseller) :AllowedRole(subscriberadmin) {
my ($self, $c, $devprof_id) = @_;
unless(is_int($devprof_id)) {
NGCP::Panel::Utils::Message::error(
c => $c,
error => "invalid device profile id '$devprof_id'",
desc => $c->loc('Invalid device profile id'),
);
NGCP::Panel::Utils::Navigation::back_or($c, $c->uri_for('/device'));
}
$c->stash->{devprof} = $c->stash->{devprof_rs}->find($devprof_id);
unless($c->stash->{devprof}) {
NGCP::Panel::Utils::Message::error(
c => $c,
error => "device profile with id '$devprof_id' not found",
desc => $c->loc('Device profile not found'),
);
NGCP::Panel::Utils::Navigation::back_or($c, $c->uri_for('/device'));
}
}
sub devprof_extensions :Chained('devprof_base') :PathPart('extensions') :Args(0):Does(ACL) :ACLDetachTo('/denied_page') :AllowedRole(admin) :AllowedRole(reseller) :AllowedRole(subscriberadmin) {
my ($self, $c) = @_;
my $rs = $c->stash->{devprof}->config->device->autoprov_extensions_link;
my $device_info = { $c->stash->{devprof}->config->device->get_inflated_columns };
foreach(qw/front_image mac_image front_thumbnail/){
delete $device_info->{$_};
}
my $data = {
'device' => $device_info,
'profile' => { $c->stash->{devprof}->get_inflated_columns},
'extensions' => { map {
$_->extension->id => {
$_->extension->get_inflated_columns,
'ranges' => [
map {
$_->get_inflated_columns,
'annotations' => [
map {{
$_->get_inflated_columns,
}} $_->annotations->all,
],
} $_->extension->autoprov_device_line_ranges->all
],
}
} $rs->all },
};
$c->stash(
aaData => $data,
iTotalRecords => 1,
iTotalDisplayRecords => 1,
iTotalRecordCountClipped => \0,
iTotalDisplayRecordCountClipped => \0,
sEcho => int($c->request->params->{sEcho} // 1),
);
$c->detach( $c->view("JSON") );
}
sub devprof_get_lines :Chained('devprof_base') :PathPart('lines/ajax') :Args(0) :Does(ACL) :ACLDetachTo('/denied_page') :AllowedRole(admin) :AllowedRole(reseller) :AllowedRole(subscriberadmin) {
my ($self, $c) = @_;
my $resultset = $c->stash->{devprof}->config->device->autoprov_device_line_ranges;
my $cols = NGCP::Panel::Utils::Datatables::set_columns($c, [
{ name => 'id', search => 1, title => $c->loc('ID') },
{ name => 'name', search => 1, title => $c->loc('Name') },
{ name => 'num_lines', search => 1, title => $c->loc('Number of Lines/Keys') },
{ name => 'can_private', search => 1, title => $c->loc('Private Line') },
{ name => 'can_shared', search => 1, title => $c->loc('Shared Line') },
{ name => 'can_blf', search => 1, title => $c->loc('BLF Key') },
{ name => 'can_speeddial', search => 1, title => $c->loc('SpeedDial') },
{ name => 'can_forward', search => 1, title => $c->loc('Forward') },
{ name => 'can_transfer', search => 1, title => $c->loc('Transfer') },
]);
NGCP::Panel::Utils::Datatables::process($c, $resultset, $cols);
$c->detach( $c->view("JSON") );
}
sub devprof_get_annotated_info :Chained('devprof_base') :PathPart('annolines/ajax') :Args(0) :Does(ACL) :ACLDetachTo('/denied_page') :AllowedRole(admin) :AllowedRole(reseller) :AllowedRole(subscriberadmin) {
my ($self, $c) = @_;
$self->get_annotated_info($c, $c->stash->{devprof}->config->device );
}
sub devmod_get_annotated_info :Chained('devmod_base') :PathPart('annolines/ajax') :Args(0) :Does(ACL) :ACLDetachTo('/denied_page') :AllowedRole(admin) :AllowedRole(reseller) :AllowedRole(subscriberadmin) {
my ($self, $c) = @_;
$self->get_annotated_info($c, $c->stash->{devmod} );
}
sub get_annotated_info :Private {
my ($self, $c, $devmod) = @_;
my $device_info = { $devmod->get_inflated_columns };
foreach(qw/front_image mac_image/){
delete $device_info->{$_};
}
my $gather_ranges_info = sub {
my $rs = shift;
return [ map {
{
$_->get_inflated_columns,
'annotations' => [
map {{
$_->get_inflated_columns,
}} $_->annotations->all,
],
}
}$rs->all
];
};
my $data = {
'device' => $device_info,
'ranges' => $gather_ranges_info->( $devmod->autoprov_device_line_ranges ),
'extensions' => { map {
$_->extension->id => {
$_->extension->get_inflated_columns,
'ranges' => $gather_ranges_info->( $_->extension->autoprov_device_line_ranges ),
}
} $devmod->autoprov_extensions_link->all },
};
$c->stash(
aaData => $data,
iTotalRecords => 1,
iTotalDisplayRecords => 1,
iTotalRecordCountClipped => \0,
iTotalDisplayRecordCountClipped => \0,
sEcho => int($c->request->params->{sEcho} // 1),
);
$c->detach( $c->view("JSON") );
}
sub devprof_delete :Chained('devprof_base') :PathPart('delete') :Args(0) :Does(ACL) :ACLDetachTo('/denied_page') :AllowedRole(admin) :AllowedRole(reseller) {
my ($self, $c) = @_;
try {
$c->stash->{devprof}->delete;
NGCP::Panel::Utils::Message::info(
c => $c,
data => { $c->stash->{devprof}->get_inflated_columns },
desc => $c->loc('Device profile successfully deleted'),
);
} catch($e) {
NGCP::Panel::Utils::Message::error(
c => $c,
error => "failed to delete device profile with id '".$c->stash->{devprof}->id."': $e",
desc => $c->loc('Failed to delete device profile'),
);
}
NGCP::Panel::Utils::Navigation::back_or($c, $c->uri_for('/device'));
}
sub devprof_edit :Chained('devprof_base') :PathPart('edit') :Args(0) :Does(ACL) :ACLDetachTo('/denied_page') :AllowedRole(admin) :AllowedRole(reseller) {
my ($self, $c) = @_;
my $posted = ($c->request->method eq 'POST');
my $form;
my $params = { $c->stash->{devprof}->get_inflated_columns };
$params->{config}{id} = delete $params->{config_id};
$params = merge($params, $c->session->{created_objects});
$form = NGCP::Panel::Form::get("NGCP::Panel::Form::Device::Profile", $c);
$form->process(
posted => $posted,
params => $c->request->params,
item => $params
);
NGCP::Panel::Utils::Navigation::check_form_buttons(
c => $c,
form => $form,
fields => {
'config.create' => $c->uri_for('/device/config/create'),
},
back_uri => $c->req->uri,
);
if($posted && $form->validated) {
try {
my $schema = $c->model('DB');
$schema->txn_do(sub {
$form->values->{config_id} = $form->values->{config}{id};
delete $form->values->{config};
$c->stash->{devprof}->update($form->values);
delete $c->session->{created_objects}->{config};
});
NGCP::Panel::Utils::Message::info(
c => $c,
data => { $c->stash->{devprof}->get_inflated_columns },
desc => $c->loc('Successfully updated device profile'),
);
} catch($e) {
NGCP::Panel::Utils::Message::error(
c => $c,
error => $e,
desc => $c->loc('Failed to update device profile'),
);
}
NGCP::Panel::Utils::Navigation::back_or($c, $c->uri_for('/device'));
}
$c->stash(
devprof_edit_flag => 1,
form => $form,
);
}
sub dev_field_ajax :Chained('base') :PathPart('device/ajax') :Args(0) :Does(ACL) :ACLDetachTo('/denied_page') :AllowedRole(admin) :AllowedRole(reseller) {
my ($self, $c) = @_;
my $resultset = $c->stash->{fielddev_rs};
NGCP::Panel::Utils::Datatables::process($c, $resultset, $c->stash->{fielddev_dt_columns});
$c->detach( $c->view("JSON") );
}
sub dev_field_config :Chained('/') :PathPart('device/autoprov/config') :Args() {
my ($self, $c, $id) = @_;
my $opt = $c->req->params->{opt};
delete $c->response->cookies->{ngcp_panel_session};
$c->response->headers->remove_header('Connection');
$c->response->headers->remove_header('X-Catalyst');
$c->response->headers->push_header('Last-Modified' => DateTime::Format::HTTP->format_datetime());
$c->log->debug("SSL_CLIENT_M_DN: " . ($c->request->env->{SSL_CLIENT_M_DN} // ""));
unless(
($c->user_exists && ($c->user->roles eq "admin" || $c->user->roles eq "reseller")) ||
defined $c->request->env->{SSL_CLIENT_M_DN}
) {
$c->log->info("unauthenticated config access to id '$id' via ip " . $c->qs($c->req->address));
$c->response->content_type('text/plain');
if($c->config->{features}->{debug}) {
$c->response->body("403 - unauthenticated config access");
} else {
$c->response->body("403 - forbidden");
}
$c->response->status(403);
return;
}
unless($id) {
$c->response->content_type('text/plain');
if($c->config->{features}->{debug}) {
$c->response->body("404 - device id not given");
} else {
$c->response->body("404 - device not found");
}
$c->response->status(404);
return;
}
if($id =~ /^([0-9a-f]{12})\-directory\.xml$/) {
$c->log->debug("identified bootstrap path part '$id' as polycom directory request");
$c->res->redirect($c->uri_for_action("/pbx/polycom_directory_list", $id));
return;
}
my $filename = $id;
$id =~ s/\.(cfg|ini|xml)$//;
$id =~ s/^config\.//;
$id =~ s/^([^\=]+)\=0$/$1/;
$id = lc $id;
$id =~ s/\-[a-z]+$//;
$id =~ s/\-//g;
=pod
my $yealink_key;
if($id =~ s/_secure\.enc$//) {
# mark to serve master-encrypted device key instead of config
$yealink_key = $c->config->{autoprovisioning}->{yealink_key};
}
=cut
# print access details for external rate limiting (e.g. fail2ban)
my $ip;
if(defined $c->req->headers->header('X-Forwarded-For')) {
$ip = (split(/\s*,\s*/, $c->req->headers->header('X-Forwarded-For')))[0];
} else {
$ip = $c->req->address;
}
# example DN format is:
# /C=US/ST=708105B37234/L=CBT153908BX/O=Cisco Systems, Inc./OU=cisco.com/CN=SPA-525G2, MAC: 708105B37234, Serial: CBT153908BX/emailAddress=linksys-certadmin@cisco.com
# if check is enabled, lowercase both DN and given MAC, strip colons and dashes from both, and try to
# find given MAC as substring in DN
if ($c->config->{security}->{autoprov_ssl_mac_check}) {
my $dn = $c->request->env->{SSL_CLIENT_M_DN} // '';
$dn = lc($dn);
$dn =~ s/[:\-]//g;
if (index($dn, $id) == -1) {
$c->log->info("unauthorized config access to id '$id' from dn '" . $c->qs($dn) . "' via ip '" . $c->qs($ip) . "'");
$c->response->content_type('text/plain');
if($c->config->{features}->{debug}) {
$c->response->body("403 - unauthorized config access");
} else {
$c->response->body("403 - forbidden");
}
$c->response->status(403);
return;
}
}
my $dev = $c->model('DB')->resultset('autoprov_field_devices')->find({
identifier => $id
});
unless($dev) {
$c->log->warn("Unknown autoprov config '$id' for '" . $c->qs($ip) . "'");
$c->response->content_type('text/plain');
if($c->config->{features}->{debug}) {
$c->response->body("404 - device id '" . $id . "' not found");
} else {
$c->response->body("404 - device not found");
}
$c->response->status(404);
return;
}
$c->log->info("Serving autoprov config for '$id' to '" . $c->qs($ip) . "'");
=pod
if(defined $yealink_key && defined $dev->encryption_key) {
my $cipher = Crypt::Rijndael->new(
$yealink_key, Crypt::Rijndael::MODE_ECB()
);
$c->response->content_type('text/plain');
$c->response->body($cipher->encrypt($dev->encryption_key));
$c->response->status(200);
return;
}
=cut
my $model = $dev->profile->config->device;
my $schema = 'https';
my $host = $c->config->{deviceprovisioning}->{host} // $c->req->uri->host;
my $port = $c->config->{deviceprovisioning}->{port} // 1444;
my $cisco_port = $c->config->{deviceprovisioning}->{cisco_port} // 1447;
my $boot_port = $c->config->{deviceprovisioning}->{bootstrap_port} // 1445;
my $config_port = $dev->profile->config->device->bootstrap_method eq "http" ?
$cisco_port : $port;
my $vars = {
opt => $opt,
config => {
url => "$schema://$host:$config_port/device/autoprov/config/$id",
baseurl => "$schema://$host:$config_port/device/autoprov/config/",
mac => $id,
filename => $filename,
},
firmware => {
},
phone => {
vendor => $model->vendor,
model => $model->model,
stationname => $dev->station_name,
lineranges => [],
},
directory => {
spaurl => "$schema://$host:$cisco_port/pbx/directory/spa/$id",
panaurl => "$schema://$host:$port/pbx/directory/panasonic",
yeaurl => "$schema://$host:$port/pbx/directory/yealink?userid=$id",
name => 'PBX Address Book',
},
ldap => {
tls => $c->config->{ldap_device}->{tls},
ip => $c->config->{ldap_device}->{ip},
port => $c->config->{ldap_device}->{port},
dn => ',dc=hpbx,dc=sipwise,dc=com', # uid=xxx,o=contract-id added below
password => '', # set below
base => ',dc=hpbx,dc=sipwise,dc=com', # o=contract-id added below
nameattr => 'displayName',
phoneattr => 'telephoneNumber'
}
};
$vars->{firmware}->{baseurl} = "$schema://$host:$config_port/device/autoprov/firmware";
$vars->{firmware}->{booturl} = "http://$host:$boot_port/device/autoprov/firmware";
my $latest_fw = $c->model('DB')->resultset('autoprov_firmwares')->search({
device_id => $model->id,
}, {
order_by => { -desc => 'version' },
})->first;
if($latest_fw) {
$vars->{firmware}->{maxversion} = $latest_fw->version;
}
my $ldap_attr_set = 0;
my @lines = ();
my @field_models = $c->model('DB')->resultset('autoprov_devices')->search_rs({
'autoprov_field_device_lines.device_id' => $dev->id,
},
{
'join' => {'autoprov_device_line_ranges' => 'autoprov_field_device_lines'},
'+select' => [
{'' => \['me.type = ?', [ {} => 'phone' ] ], '-as' => 'is_phone_model' },
],
'+as' => ['is_phone_model'],
'group_by' => 'me.id',
'order_by' => { -desc => 'is_phone_model' },
}
)->all ;
foreach my $linerange( map { $_->autoprov_device_line_ranges->all } @field_models ) {
my $range = {
name => $linerange->name,
num_lines => $linerange->num_lines,
lines => [],
};
foreach my $line($linerange->autoprov_field_device_lines->search({ device_id => $dev->id })->all) {
my $sub = $line->provisioning_voip_subscriber;
my %sub_preferences_vars = (
display_name => $sub->username,
concurrent_max => 0,
concurrent_max_per_account => 0,
cc => '',
ac => '',
);
my $pref_rs = NGCP::Panel::Utils::Preferences::get_preferences_rs(
c => $c,
id => $sub->id,
type => 'usr',
#attribute => [keys %sub_preferences_vars],
);
my $preferences = get_inflated_columns_all($pref_rs, 'hash' => 'attribute', 'column' => 'value' );
foreach my $key (keys %sub_preferences_vars){
if(exists $preferences->{$key}){
$sub_preferences_vars{$key} = $preferences->{$key};
}
}
$sub_preferences_vars{displayname} = delete $sub_preferences_vars{display_name};
$sub_preferences_vars{device_id} = $line->deviceid_alias ? $line->deviceid_alias->username : undef;
# TODO: only push password for private/shared line?
my $aliases = [ $sub->voip_dbaliases->search({ is_primary => 0 })->get_column("username")->all ];
my $primary = $sub->voip_dbaliases->search({ is_primary => 1 })->get_column("username")->first;
push @{ $range->{lines} }, {
alias_numbers => $aliases,
primary_number => $primary,
extension => $sub->pbx_extension,
username => $sub->username,
domain => $sub->domain->domain,
password => $sub->password,
keynum => $line->key_num,
type => $line->line_type,
preferences => $preferences,
target_number => $line->target_number,
%sub_preferences_vars,
};
if(!$ldap_attr_set && $linerange->name eq "Full Keys" && $line->line_type eq "private") {
$vars->{ldap}->{dn} = "uid=".$sub->uuid . ",o=" . $sub->account_id . $vars->{ldap}->{dn};
$vars->{ldap}->{base} = "o=" . $sub->account_id . $vars->{ldap}->{base};
$vars->{ldap}->{password} = $sub->password;
$ldap_attr_set = 1;
}
}
push @{ $vars->{phone}->{lineranges} }, $range;
}
my $preferences_device;
my $preferences_device_dynamic;
my $preferences_id = {
'fielddev' => $dev->id,
'dev' => $model->id,
'devprof' => $dev->profile->id,
};
foreach my $type (keys %$preferences_id) {
my $pref_rs = NGCP::Panel::Utils::Preferences::get_preferences_rs(
c => $c,
id => $preferences_id->{$type},
type => $type,
);
my $pref_rs_dynamic = $pref_rs->search_rs({
'attribute.dynamic' => 1,
}, {
'+select' => [\'replace(attribute.attribute,"__","")' ],
'+as' => ['attribute_normalized'],
});
$preferences_device_dynamic->{$type} = get_inflated_columns_all($pref_rs_dynamic,
'hash' => 'attribute_normalized',
'column' => 'value'
);
my $pref_rs_static = $pref_rs->search_rs({
'attribute.dynamic' => 0,
});
$preferences_device->{$type} = get_inflated_columns_all($pref_rs_static,
'hash' => 'attribute',
'column' => 'value'
);
}
my %preferences_device = (
'model' => $preferences_device->{dev},
'profile' => $preferences_device->{devprof},
'persistent' => {
'model' => {%{$preferences_device->{dev}}},
'profile' => {%{$preferences_device->{devprof}}},
'device' => $preferences_device->{fielddev},
},
'dynamic' => {
'model' => $preferences_device_dynamic->{dev},
'profile' => $preferences_device_dynamic->{devprof},
'device' => $preferences_device_dynamic->{fielddev},
},
'device' => {
%{$preferences_device->{dev}},
%{$preferences_device_dynamic->{dev}},
%{$preferences_device->{devprof}},
%{$preferences_device_dynamic->{devprof}},
%{$preferences_device->{fielddev}},
%{$preferences_device_dynamic->{fielddev}},
}
);
$vars->{preferences} //= {};
$vars->{preferences}->{device} = \%preferences_device;
my $data = $dev->profile->config->data;
my $var_hash = md5_hex(freeze $vars);
my $cfg_hash = md5_hex($data);
$vars->{checksum} = md5_hex($var_hash . $cfg_hash);
my $processed_data = "";
my $t = Template->new({
PLUGIN_BASE => 'NGCP::Panel::Template::Plugin',
});
$t->process(\$data, $vars, \$processed_data) || do {
my $error = $t->error();
my $msg = "error processing template, type=".$error->type.", info='".$error->info."'";
$c->log->error($msg);
$c->response->body("500 - error creating template:\n$msg");
$c->response->status(500);
return;
};
if($model->vendor eq "Audiocodes") {
$processed_data .= "\r\n\r\n";
}
$c->log->debug("providing config to $id");
$c->log->debug($processed_data);
=pod
if(defined $dev->encryption_key) {
# yealink uses weak ECB mode, but well...
my $cipher = Crypt::Rijndael->new(
$dev->encryption_key, Crypt::Rijndael::MODE_ECB()
);
$c->response->content_type("application/octet-stream");
$c->response->body($cipher->encrypt($processed_data));
} else {
=cut
my $result = $self->dev_field_encrypt( $c, $dev, $processed_data, $vars);
$c->response->content_type($result->{content_type});
$c->response->body(${$result->{content}});
}
sub dev_field_encrypt :Private{
my ($self, $c, $dev, $processed_data, $vars) = @_;
my $result = {'content' => \$processed_data };
my $model = $dev->profile->config->device;
my $module = 'NGCP::Panel::Utils::Device';
my $method = $module->can(lc($model->vendor.'_fielddev_config_process'));
if ( $method ) {
$result->{content_type} //= 'application/octet-stream';
$method->(\$processed_data, $result, 'field_device' => $dev, 'vars' => $vars );
}else{
$result->{content_type} //= $dev->profile->config->content_type;
}
return $result;
}
sub dev_field_bootstrap :Chained('/') :PathPart('device/autoprov/bootstrap') :Args() {
my ($self, $c, @id) = @_;
my $opt = $c->req->params->{opt};
my $id;
delete $c->response->cookies->{ngcp_panel_session};
$c->response->headers->remove_header('Connection');
$c->response->headers->remove_header('X-Catalyst');
$c->response->headers->push_header('Last-Modified' => DateTime::Format::HTTP->format_datetime());
foreach my $did (@id) {
$c->log->debug("checking bootstrap path part '$did'");
$did =~ s/\.(cfg|ini|xml)$//;
$did =~ s/^config\.//;
$did =~ s/^([^\=]+)\=0$/$1/;
$did = lc $did;
$did =~ s/\-[a-z]+$//;
$did =~ s/\-//g;
if($did =~ /^[0-9a-f]{12}$/) {
$c->log->debug("identified bootstrap path part '$did' as valid device id");
$id = $did;
last;
}
}
unless($id) {
$c->response->content_type('text/plain');
if($c->config->{features}->{debug}) {
$c->response->body("404 - device id not given");
} else {
$c->response->body("404 - device not found");
}
$c->response->status(404);
return;
}
my $ip;
if(defined $c->req->headers->header('X-Forwarded-For')) {
$ip = (split(/\s*,\s*/, $c->req->headers->header('X-Forwarded-For')))[0];
} else {
$ip = $c->req->address;
}
my $dev = $c->model('DB')->resultset('autoprov_field_devices')->find({
identifier => $id
});
unless($dev) {
$c->log->warn("Unknown autoprov bootstrap config '$id' for '" . $c->qs($ip) . "'");
$c->response->content_type('text/plain');
if($c->config->{features}->{debug}) {
$c->response->body("404 - device id '" . $id . "' not found");
} else {
$c->response->body("404 - device not found");
}
$c->response->status(404);
return;
}
$c->log->info("Serving autoprov bootstrap config for '$id' to '" . $c->qs($ip) . "'");
my $model = $dev->profile->config->device;
my $schema = 'https';
my $host = $c->config->{deviceprovisioning}->{host} // $c->req->uri->host;
my $port = $c->config->{deviceprovisioning}->{port} // 1444;
my $boot_port = $c->config->{deviceprovisioning}->{bootstrap_port} // 1445;
my $cisco_port = $c->config->{deviceprovisioning}->{cisco_port} // 1447;
my $config_port = $dev->profile->config->device->bootstrap_method eq "http" ?
$cisco_port : $port;
my $vars = {
opt => $opt,
config => {
url => "$schema://$host:$config_port/device/autoprov/config/$id",
baseurl => "$schema://$host:$config_port/device/autoprov/config/",
caurl => "http://$host:$boot_port/device/autoprov/cacert",
ca => $c->model('CA')->get_provisioning_ca_cert($c),
mac => $id,
bootstrap => 1,
},
firmware => {
baseurl => "$schema://$host:$config_port/device/autoprov/firmware",
booturl => "http://$host:$boot_port/device/autoprov/firmware",
},
};
my $latest_fw = $c->model('DB')->resultset('autoprov_firmwares')->search({
device_id => $model->id,
}, {
order_by => { -desc => 'version' },
})->first;
if($latest_fw) {
$vars->{firmware}->{maxversion} = $latest_fw->version;
}
my $data = $dev->profile->config->data;
my $processed_data = "";
my $t = Template->new({
PLUGIN_BASE => 'NGCP::Panel::Template::Plugin',
});
$t->process(\$data, $vars, \$processed_data) || do {
my $error = $t->error();
my $msg = "error processing template, type=".$error->type.", info='".$error->info."'";
$c->log->error($msg);
$c->response->body("500 - error creating template:\n$msg");
$c->response->status(500);
return;
};
if($model->vendor eq "Audiocodes") {
$processed_data .= "\r\n\r\n";
}
$c->log->debug("providing config to $id");
$c->log->debug($processed_data);
my $result = $self->dev_field_encrypt( $c, $dev, $processed_data, $vars);
$c->response->content_type($result->{content_type});
$c->response->body(${$result->{content}});
}
sub dev_servercert :Chained('/') :PathPart('device/autoprov/servercert') :Args(0) {
my ($self, $c) = @_;
my $cert = $c->model('CA')->get_provisioning_server_cert($c);
$c->res->headers(HTTP::Headers->new(
'Content-Type' => 'application/octet-stream',
#'Content-Disposition' => sprintf('attachment; filename=%s', "provisioning-certificate.pem")
));
$c->res->body($cert);
return;
}
sub dev_cacert :Chained('/') :PathPart('device/autoprov/cacert') :Args(0) {
my ($self, $c) = @_;
my $cert = $c->model('CA')->get_provisioning_ca_cert($c);
$c->res->headers(HTTP::Headers->new(
'Content-Type' => 'application/octet-stream',
#'Content-Disposition' => sprintf('attachment; filename=%s', "provisioning-certificate.pem")
));
$c->res->body($cert);
return;
}
sub dev_field_firmware_base :Chained('/') :PathPart('device/autoprov/firmware') :CaptureArgs(1) {
my ($self, $c, $id) = @_;
unless($id) {
$c->response->content_type('text/plain');
if($c->config->{features}->{debug}) {
$c->response->body("404 - device id not given");
} else {
$c->response->body("404 - device not found");
}
$c->response->status(404);
$c->detach();
return;
}
$id =~ s/^([^\=]+)\=0$/$1/;
$id = lc $id;
my $dev = $c->model('DB')->resultset('autoprov_field_devices')->find({
identifier => $id
});
unless($dev) {
$c->response->content_type('text/plain');
if($c->config->{features}->{debug}) {
$c->response->body("404 - device id '" . $id . "' not found");
} else {
$c->response->body("404 - device not found");
}
$c->response->status(404);
$c->detach();
return;
}
$c->stash->{dev} = $dev;
}
sub dev_field_firmware_download :Chained('dev_field_firmware_base') :PathPart('version') :Args(1) {
my ($self, $c, $ver) = @_;
my $tag = "";
my $rs = $c->stash->{dev}->profile->config->device->autoprov_firmwares->search({
device_id => $c->stash->{dev}->profile->config->device->id,
version => { '=' => $ver },
});
unless($rs->first) {
$tag = $ver;
$rs = $c->stash->{dev}->profile->config->device->autoprov_firmwares->search({
device_id => $c->stash->{dev}->profile->config->device->id,
tag => { '=' => $tag },
});
}
my $fw = $rs->first;
unless($fw) {
$c->response->content_type('text/plain');
if(defined $tag && length $tag > 0) {
$c->response->body("404 - firmware tag '$tag' is latest");
} else {
$c->response->body("404 - firmware version '$ver' is latest");
}
$c->response->status(404);
return;
}
$c->response->header ('Content-Disposition' => 'attachment; filename="' . $fw->filename . '"');
$c->response->content_type('application/octet-stream');
NGCP::Panel::Utils::DeviceFirmware::get_firmware_data_into_body(
c => $c,
fw_id => $fw->id
);
}
sub dev_field_firmware_version_base :Chained('dev_field_firmware_base') :PathPart('from') :CaptureArgs(1) {
my ($self, $c, $fwver) = @_;
unless(defined $fwver) {
$c->response->content_type('text/plain');
if($c->config->{features}->{debug}) {
$c->response->body("404 - firmware name not given");
} else {
$c->response->body("404 - firmware not found");
}
$c->response->status(404);
$c->detach();
return;
}
$c->stash->{dev_fw_string} = $fwver;
my $dev = $c->stash->{dev};
$c->stash->{fw_rs} = $dev->profile->config->device->autoprov_firmwares;
}
sub dev_field_firmware_next :Chained('dev_field_firmware_version_base') :PathPart('next') :Args {
my ($self, $c, $tag) = @_;
my $rs = $c->stash->{fw_rs}->search({
device_id => $c->stash->{dev}->profile->config->device->id,
version => { '>' => $c->stash->{dev_fw_string} },
}, {
order_by => { -asc => 'version' },
});
if(defined $tag && length $tag > 0) {
$rs = $rs->search({
tag => $tag,
});
}
if(defined $c->req->params->{q}) {
$rs = $rs->search({
version => { 'like' => $c->req->params->{q} . '%' },
});
}
my $fw = $rs->first;
unless($fw) {
$c->response->content_type('text/plain');
if(defined $tag && length $tag > 0) {
$c->response->body("404 - current firmware with tag '$tag' is latest");
} else {
$c->response->body("404 - current firmware version '" . $c->stash->{dev_fw_string} . "' is latest");
}
$c->response->status(404);
return;
}
$c->response->header ('Content-Disposition' => 'attachment; filename="' . $fw->filename . '"');
$c->response->content_type('application/octet-stream');
NGCP::Panel::Utils::DeviceFirmware::get_firmware_data_into_body(
c => $c,
fw_id => $fw->id
);
}
sub dev_field_firmware_latest :Chained('dev_field_firmware_version_base') :PathPart('latest') :Args {
my ($self, $c, $tag) = @_;
my $rs = $c->stash->{fw_rs}->search({
device_id => $c->stash->{dev}->profile->config->device->id,
version => { '>' => $c->stash->{dev_fw_string} },
}, {
order_by => { -desc => 'version' },
});
if(defined $tag && length $tag > 0) {
$rs = $rs->search({
tag => $tag,
});
}
if(defined $c->req->params->{q}) {
$rs = $rs->search({
version => { 'like' => $c->req->params->{q} . '%' },
});
}
my $fw = $rs->first;
unless($fw) {
$c->response->content_type('text/plain');
if(defined $tag && length $tag > 0) {
$c->response->body("404 - current firmware with tag '$tag' is latest");
} else {
$c->response->body("404 - current firmware version '" . $c->stash->{dev_fw_string} . "' is latest");
}
$c->response->status(404);
return;
}
$c->response->header ('Content-Disposition' => 'attachment; filename="' . $fw->filename . '"');
$c->response->content_type('application/octet-stream');
NGCP::Panel::Utils::DeviceFirmware::get_firmware_data_into_body(
c => $c,
fw_id => $fw->id
);
}
sub devices_preferences_list :Chained('devmod_base') :PathPart('preferences') :CaptureArgs(0) {
my ($self, $c) = @_;
my $dev_pref_rs = NGCP::Panel::Utils::Preferences::get_preferences_rs(
c => $c,
type => 'dev',
id => $c->stash->{devmod}->id,
);
my $pref_values = get_inflated_columns_all($dev_pref_rs,'hash' => 'attribute', 'column' => 'value', 'force_array' => 1);
NGCP::Panel::Utils::Preferences::load_preference_list(
c => $c,
pref_values => $pref_values,
#we don't need fielddev_pref flag, because it always will be just more narrow than dev_pref.
dev_pref => 1,
search_conditions => [{
'attribute' =>
[ -or =>
{ 'like' => 'vnd_'.lc($c->stash->{devmod}->vendor).'%' },
{'-not_like' => 'vnd_%' },
],
#relation type is defined by preference flag dev_pref,
#so here we select only linked to the current model, or not linked to any model at all
'-or' => [
'voip_preference_relations.autoprov_device_id' => $c->stash->{devmod}->id,
'voip_preference_relations.reseller_id' => $c->stash->{devmod}->reseller_id,
'voip_preference_relations.voip_preference_id' => undef
],
},{
join => {'voip_preferences' => 'voip_preference_relations'},
}
]
);
$c->stash(template => 'device/preferences.tt');
return;
}
sub devices_preferences_root :Chained('devices_preferences_list') :PathPart('') :Args(0) {
return;
}
sub devices_preferences_base :Chained('devices_preferences_list') :PathPart('') :CaptureArgs(1) {
my ($self, $c, $pref_id) = @_;
$c->stash->{preference_meta} = $c->model('DB')
->resultset('voip_preferences')
->search({
-or => ['voip_preferences_enums.dev_pref' => 1,
'voip_preferences_enums.dev_pref' => undef],
},{
prefetch => 'voip_preferences_enums',
})
->find({id => $pref_id});
$c->stash->{preference} = $c->model('DB')
->resultset('voip_dev_preferences')
->search({
'attribute_id' => $pref_id,
'device_id' => $c->stash->{devmod}->id,
});
return;
}
sub devices_preferences_create :Chained('devices_preferences_list') :PathPart('create') :Args(0) {
my ($self, $c) = @_;
my $posted = ($c->request->method eq 'POST');
my $form = NGCP::Panel::Form::get("NGCP::Panel::Form::Device::Preference", $c);
my $params = {};
$params = merge($params, $c->session->{created_objects});
$form->process(
posted => $posted,
params => $c->request->params,
item => $params
);
if($posted && $form->validated) {
try {
my $schema = $c->model('DB');
$schema->txn_do(sub {
my $resource = $form->values;
$resource->{dev_pref} = 1;
$resource->{autoprov_device_id} = $c->stash->{devmod}->id;
my $preference = NGCP::Panel::Utils::Preferences::create_dynamic_preference(
$c, $resource,
group_name => 'CPBX Device Administration',
);
$c->session->{created_objects}->{preference} = { id => $preference->id };
});
NGCP::Panel::Utils::Message::info(
c => $c,
desc => $c->loc('Successfully created device model preference'),
);
} catch($e) {
NGCP::Panel::Utils::Message::error(
c => $c,
error => $e,
desc => $c->loc('Failed to create device model preference'),
);
}
NGCP::Panel::Utils::Navigation::back_or($c, $c->uri_for('/device'));
}
$c->stash(
create_flag => 1,
form => $form,
);
}
sub devices_preferences_editmeta :Chained('devices_preferences_base') :PathPart('editmeta') :Args(0) {
my ($self, $c) = @_;
my $posted = ($c->request->method eq 'POST');
my $form = NGCP::Panel::Form::get("NGCP::Panel::Form::Device::Preference", $c);
my $params = { $c->stash->{preference_meta}->get_inflated_columns };
$params->{enum} = [ map { {$_->get_inflated_columns} } $c->stash->{preference_meta}->voip_preferences_enums->all ];
$params = merge($params, $c->session->{created_objects});
$form->process(
posted => $posted,
params => $c->request->params,
item => $params
);
if($posted && $form->validated) {
try {
my $schema = $c->model('DB');
$schema->txn_do(sub {
my $resource = $form->values;
$resource->{dev_pref} = 1;
$resource->{autoprov_device_id} = $c->stash->{devmod}->id;
NGCP::Panel::Utils::Preferences::update_dynamic_preference(
$c, $c->stash->{preference_meta}, $resource
);
});
NGCP::Panel::Utils::Message::info(
c => $c,
desc => $c->loc('Successfully updated device model preference'),
);
} catch($e) {
NGCP::Panel::Utils::Message::error(
c => $c,
error => $e,
desc => $c->loc('Failed to update device model preference'),
);
}
NGCP::Panel::Utils::Navigation::back_or($c, $c->uri_for('/device'));
}
$c->stash(
editmeta_flag => 1,
form => $form,
);
}
sub devices_preferences_delete :Chained('devices_preferences_base') :PathPart('delete') :Args(0):Does(ACL) :ACLDetachTo('/denied_page') :AllowedRole(admin) :AllowedRole(reseller) {
my ($self, $c) = @_;
try {
NGCP::Panel::Utils::Preferences::delete_dynamic_preference(
$c, $c->stash->{preference_meta}
);
NGCP::Panel::Utils::Message::info(
c => $c,
data => { id => $c->stash->{preference_meta}->id,
attribute => $c->stash->{preference_meta}->attribute },
desc => $c->loc('Device model preference successfully deleted'),
);
} catch($e) {
NGCP::Panel::Utils::Message::error(
c => $c,
error => "failed to delete device model preference with id '".$c->stash->{preference_meta}->id."': $e",
desc => $c->loc('Failed to delete device model preference'),
);
}
NGCP::Panel::Utils::Navigation::back_or($c, $c->uri_for('/device'));
}
sub devices_preferences_edit :Chained('devices_preferences_base') :PathPart('edit') :Args(0) {
my ($self, $c) = @_;
$c->stash(edit_preference => 1);
my @enums = $c->stash->{preference_meta}
->voip_preferences_enums
->all;
my $pref_rs = $c->stash->{devmod}->voip_dev_preferences;
NGCP::Panel::Utils::Preferences::create_preference_form(
c => $c,
pref_rs => $pref_rs,
enums => \@enums,
base_uri => $c->uri_for_action('/device/devices_preferences_root', [@{ $c->req->captures }[0]] ),
edit_uri => $c->uri_for_action('/device/devices_preferences_edit', $c->req->captures ),
);
return;
}
sub profile_preferences_list :Chained('devprof_base') :PathPart('preferences') :CaptureArgs(0) {
my ($self, $c) = @_;
my $devprof_pref_rs = NGCP::Panel::Utils::Preferences::get_preferences_rs(
c => $c,
type => 'devprof',
id => $c->stash->{devprof}->id,
);
my $pref_values = get_inflated_columns_all($devprof_pref_rs,'hash' => 'attribute', 'column' => 'value', 'force_array' => 1);
NGCP::Panel::Utils::Preferences::load_preference_list(
c => $c,
pref_values => $pref_values,
'devprof_pref' => 1,
search_conditions => {
'-or' => [
{'attribute' => {'like' => 'vnd_'.lc($c->stash->{devprof}->config->device->vendor).'%' } },
{'attribute' => {'-not_like' => 'vnd_%' }}
],
}
);
$c->stash(template => 'device/profilepreferences.tt');
return;
}
sub profile_preferences_root :Chained('profile_preferences_list') :PathPart('') :Args(0) {
return;
}
sub profile_preferences_base :Chained('profile_preferences_list') :PathPart('') :CaptureArgs(1) {
my ($self, $c, $pref_id) = @_;
$c->stash->{preference_meta} = $c->model('DB')
->resultset('voip_preferences')
->search({
-or => ['voip_preferences_enums.devprof_pref' => 1,
'voip_preferences_enums.devprof_pref' => undef],
},{
prefetch => 'voip_preferences_enums',
})
->find({id => $pref_id});
$c->stash->{preference} = $c->model('DB')
->resultset('voip_devprof_preferences')
->search({
'attribute_id' => $pref_id,
'profile_id' => $c->stash->{devprof}->id,
});
return;
}
sub profile_preferences_edit :Chained('profile_preferences_base') :PathPart('edit') :Args(0) {
my ($self, $c) = @_;
$c->stash(edit_preference => 1);
my @enums = $c->stash->{preference_meta}
->voip_preferences_enums
->all;
my $pref_rs = $c->stash->{devprof}->voip_devprof_preferences;
NGCP::Panel::Utils::Preferences::create_preference_form(
c => $c,
pref_rs => $pref_rs,
enums => \@enums,
base_uri => $c->uri_for_action('/device/profile_preferences_root', [@{ $c->req->captures }[0]] ),
edit_uri => $c->uri_for_action('/device/profile_preferences_edit', $c->req->captures ),
);
return;
}
1;
__END__
=head1 NAME
NGCP::Panel::Controller::Device
=head1 DESCRIPTION
A helper to manipulate devices data
=head1 AUTHOR
Sipwise Development Team
=head1 LICENSE
This library is free software. You can redistribute it and/or modify
it under the same terms as Perl itself.
=cut
# vim: set tabstop=4 expandtab: